From acfd22712be1e0c794655a48c5380f7cb038ae6f Mon Sep 17 00:00:00 2001 From: "Jeff Washington (jwash)" Date: Fri, 25 Mar 2022 10:37:00 -0500 Subject: [PATCH] RollingBitFIeld to its own file (#23917) --- runtime/src/accounts_index.rs | 894 +----------------------------- runtime/src/ancestors.rs | 2 +- runtime/src/lib.rs | 1 + runtime/src/rolling_bit_field.rs | 904 +++++++++++++++++++++++++++++++ 4 files changed, 907 insertions(+), 894 deletions(-) create mode 100644 runtime/src/rolling_bit_field.rs diff --git a/runtime/src/accounts_index.rs b/runtime/src/accounts_index.rs index 991e6d4c26..d98593bdbf 100644 --- a/runtime/src/accounts_index.rs +++ b/runtime/src/accounts_index.rs @@ -8,9 +8,9 @@ use { inline_spl_token::{self, GenericTokenAccount}, inline_spl_token_2022, pubkey_bins::PubkeyBinCalculator24, + rolling_bit_field::RollingBitField, secondary_index::*, }, - bv::BitVec, log::*, ouroboros::self_referencing, rand::{thread_rng, Rng}, @@ -398,258 +398,6 @@ impl PreAllocatedAccountMapEntry { } } -#[derive(Debug, Default, AbiExample, Clone)] -pub struct RollingBitField { - max_width: u64, - min: u64, - max_exclusive: u64, - bits: BitVec, - count: usize, - // These are items that are true and lower than min. - // They would cause us to exceed max_width if we stored them in our bit field. - // We only expect these items in conditions where there is some other bug in the system - // or in testing when large ranges are created. - excess: HashSet, -} - -impl PartialEq for RollingBitField { - fn eq(&self, other: &Self) -> bool { - // 2 instances could have different internal data for the same values, - // so we have to compare data. - self.len() == other.len() && { - for item in self.get_all() { - if !other.contains(&item) { - return false; - } - } - true - } - } -} - -// functionally similar to a hashset -// Relies on there being a sliding window of key values. The key values continue to increase. -// Old key values are removed from the lesser values and do not accumulate. -impl RollingBitField { - pub fn new(max_width: u64) -> Self { - assert!(max_width > 0); - assert!(max_width.is_power_of_two()); // power of 2 to make dividing a shift - let bits = BitVec::new_fill(false, max_width); - Self { - max_width, - bits, - count: 0, - min: 0, - max_exclusive: 0, - excess: HashSet::new(), - } - } - - // find the array index - fn get_address(&self, key: &u64) -> u64 { - key % self.max_width - } - - pub fn range_width(&self) -> u64 { - // note that max isn't updated on remove, so it can be above the current max - self.max_exclusive - self.min - } - - pub fn min(&self) -> Option { - if self.is_empty() { - None - } else if self.excess.is_empty() { - Some(self.min) - } else { - let mut min = if self.all_items_in_excess() { - u64::MAX - } else { - self.min - }; - for item in &self.excess { - min = std::cmp::min(min, *item); - } - Some(min) - } - } - - pub fn insert(&mut self, key: u64) { - let mut bits_empty = self.count == 0 || self.all_items_in_excess(); - let update_bits = if bits_empty { - true // nothing in bits, so in range - } else if key < self.min { - // bits not empty and this insert is before min, so add to excess - if self.excess.insert(key) { - self.count += 1; - } - false - } else if key < self.max_exclusive { - true // fits current bit field range - } else { - // key is >= max - let new_max = key + 1; - loop { - let new_width = new_max.saturating_sub(self.min); - if new_width <= self.max_width { - // this key will fit the max range - break; - } - - // move the min item from bits to excess and then purge from min to make room for this new max - let inserted = self.excess.insert(self.min); - assert!(inserted); - - let key = self.min; - let address = self.get_address(&key); - self.bits.set(address, false); - self.purge(&key); - - if self.all_items_in_excess() { - // if we moved the last existing item to excess, then we are ready to insert the new item in the bits - bits_empty = true; - break; - } - } - - true // moved things to excess if necessary, so update bits with the new entry - }; - - if update_bits { - let address = self.get_address(&key); - let value = self.bits.get(address); - if !value { - self.bits.set(address, true); - if bits_empty { - self.min = key; - self.max_exclusive = key + 1; - } else { - self.min = std::cmp::min(self.min, key); - self.max_exclusive = std::cmp::max(self.max_exclusive, key + 1); - assert!( - self.min + self.max_width >= self.max_exclusive, - "min: {}, max: {}, max_width: {}", - self.min, - self.max_exclusive, - self.max_width - ); - } - self.count += 1; - } - } - } - - /// remove key from set, return if item was in the set - pub fn remove(&mut self, key: &u64) -> bool { - if key >= &self.min { - // if asked to remove something bigger than max, then no-op - if key < &self.max_exclusive { - let address = self.get_address(key); - let get = self.bits.get(address); - if get { - self.count -= 1; - self.bits.set(address, false); - self.purge(key); - } - get - } else { - false - } - } else { - // asked to remove something < min. would be in excess if it exists - let remove = self.excess.remove(key); - if remove { - self.count -= 1; - } - remove - } - } - - fn all_items_in_excess(&self) -> bool { - self.excess.len() == self.count - } - - // after removing 'key' where 'key' = min, make min the correct new min value - fn purge(&mut self, key: &u64) { - if self.count > 0 && !self.all_items_in_excess() { - if key == &self.min { - let start = self.min + 1; // min just got removed - for key in start..self.max_exclusive { - if self.contains_assume_in_range(&key) { - self.min = key; - break; - } - } - } - } else { - // The idea is that there are no items in the bitfield anymore. - // But, there MAY be items in excess. The model works such that items < min go into excess. - // So, after purging all items from bitfield, we hold max to be what it previously was, but set min to max. - // Thus, if we lookup >= max, answer is always false without having to look in excess. - // If we changed max here to 0, we would lose the ability to know the range of items in excess (if any). - // So, now, with min updated = max: - // If we lookup < max, then we first check min. - // If >= min, then we look in bitfield. - // Otherwise, we look in excess since the request is < min. - // So, resetting min like this after a remove results in the correct behavior for the model. - // Later, if we insert and there are 0 items total (excess + bitfield), then we reset min/max to reflect the new item only. - self.min = self.max_exclusive; - } - } - - fn contains_assume_in_range(&self, key: &u64) -> bool { - // the result may be aliased. Caller is responsible for determining key is in range. - let address = self.get_address(key); - self.bits.get(address) - } - - // This is the 99% use case. - // This needs be fast for the most common case of asking for key >= min. - pub fn contains(&self, key: &u64) -> bool { - if key < &self.max_exclusive { - if key >= &self.min { - // in the bitfield range - self.contains_assume_in_range(key) - } else { - self.excess.contains(key) - } - } else { - false - } - } - - pub fn len(&self) -> usize { - self.count - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn clear(&mut self) { - let mut n = Self::new(self.max_width); - std::mem::swap(&mut n, self); - } - - pub fn max_exclusive(&self) -> u64 { - self.max_exclusive - } - - pub fn max_inclusive(&self) -> u64 { - self.max_exclusive.saturating_sub(1) - } - - pub fn get_all(&self) -> Vec { - let mut all = Vec::with_capacity(self.count); - self.excess.iter().for_each(|slot| all.push(*slot)); - for key in self.min..self.max_exclusive { - if self.contains_assume_in_range(&key) { - all.push(key); - } - } - all - } -} - #[derive(Debug)] pub struct RootsTracker { pub(crate) roots: RollingBitField, @@ -2187,646 +1935,6 @@ pub mod tests { } } - #[test] - fn test_bitfield_delete_non_excess() { - solana_logger::setup(); - let len = 16; - let mut bitfield = RollingBitField::new(len); - assert_eq!(bitfield.min(), None); - - bitfield.insert(0); - assert_eq!(bitfield.min(), Some(0)); - let too_big = len + 1; - bitfield.insert(too_big); - assert!(bitfield.contains(&0)); - assert!(bitfield.contains(&too_big)); - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.excess.len(), 1); - assert_eq!(bitfield.min, too_big); - assert_eq!(bitfield.min(), Some(0)); - assert_eq!(bitfield.max_exclusive, too_big + 1); - - // delete the thing that is NOT in excess - bitfield.remove(&too_big); - assert_eq!(bitfield.min, too_big + 1); - assert_eq!(bitfield.max_exclusive, too_big + 1); - let too_big_times_2 = too_big * 2; - bitfield.insert(too_big_times_2); - assert!(bitfield.contains(&0)); - assert!(bitfield.contains(&too_big_times_2)); - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.excess.len(), 1); - assert_eq!(bitfield.min(), bitfield.excess.iter().min().copied()); - assert_eq!(bitfield.min, too_big_times_2); - assert_eq!(bitfield.max_exclusive, too_big_times_2 + 1); - - bitfield.remove(&0); - bitfield.remove(&too_big_times_2); - assert!(bitfield.is_empty()); - let other = 5; - bitfield.insert(other); - assert!(bitfield.contains(&other)); - assert!(bitfield.excess.is_empty()); - assert_eq!(bitfield.min, other); - assert_eq!(bitfield.max_exclusive, other + 1); - } - - #[test] - fn test_bitfield_insert_excess() { - solana_logger::setup(); - let len = 16; - let mut bitfield = RollingBitField::new(len); - - bitfield.insert(0); - let too_big = len + 1; - bitfield.insert(too_big); - assert!(bitfield.contains(&0)); - assert!(bitfield.contains(&too_big)); - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.excess.len(), 1); - assert!(bitfield.excess.contains(&0)); - assert_eq!(bitfield.min, too_big); - assert_eq!(bitfield.max_exclusive, too_big + 1); - - // delete the thing that IS in excess - // this does NOT affect min/max - bitfield.remove(&0); - assert_eq!(bitfield.min, too_big); - assert_eq!(bitfield.max_exclusive, too_big + 1); - // re-add to excess - bitfield.insert(0); - assert!(bitfield.contains(&0)); - assert!(bitfield.contains(&too_big)); - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.excess.len(), 1); - assert_eq!(bitfield.min, too_big); - assert_eq!(bitfield.max_exclusive, too_big + 1); - } - - #[test] - fn test_bitfield_permutations() { - solana_logger::setup(); - let mut bitfield = RollingBitField::new(2097152); - let mut hash = HashSet::new(); - - let min = 101_000; - let width = 400_000; - let dead = 19; - - let mut slot = min; - while hash.len() < width { - slot += 1; - if slot % dead == 0 { - continue; - } - hash.insert(slot); - bitfield.insert(slot); - } - compare(&hash, &bitfield); - - let max = slot + 1; - - let mut time = Measure::start(""); - let mut count = 0; - for slot in (min - 10)..max + 100 { - if hash.contains(&slot) { - count += 1; - } - } - time.stop(); - - let mut time2 = Measure::start(""); - let mut count2 = 0; - for slot in (min - 10)..max + 100 { - if bitfield.contains(&slot) { - count2 += 1; - } - } - time2.stop(); - info!( - "{}ms, {}ms, {} ratio", - time.as_ms(), - time2.as_ms(), - time.as_ns() / time2.as_ns() - ); - assert_eq!(count, count2); - } - - #[test] - #[should_panic(expected = "assertion failed: max_width.is_power_of_two()")] - fn test_bitfield_power_2() { - let _ = RollingBitField::new(3); - } - - #[test] - #[should_panic(expected = "assertion failed: max_width > 0")] - fn test_bitfield_0() { - let _ = RollingBitField::new(0); - } - - fn setup_empty(width: u64) -> RollingBitFieldTester { - let bitfield = RollingBitField::new(width); - let hash_set = HashSet::new(); - RollingBitFieldTester { bitfield, hash_set } - } - - struct RollingBitFieldTester { - pub bitfield: RollingBitField, - pub hash_set: HashSet, - } - - impl RollingBitFieldTester { - fn insert(&mut self, slot: u64) { - self.bitfield.insert(slot); - self.hash_set.insert(slot); - assert!(self.bitfield.contains(&slot)); - compare(&self.hash_set, &self.bitfield); - } - fn remove(&mut self, slot: &u64) -> bool { - let result = self.bitfield.remove(slot); - assert_eq!(result, self.hash_set.remove(slot)); - assert!(!self.bitfield.contains(slot)); - self.compare(); - result - } - fn compare(&self) { - compare(&self.hash_set, &self.bitfield); - } - } - - fn setup_wide(width: u64, start: u64) -> RollingBitFieldTester { - let mut tester = setup_empty(width); - - tester.compare(); - tester.insert(start); - tester.insert(start + 1); - tester - } - - #[test] - fn test_bitfield_insert_wide() { - solana_logger::setup(); - let width = 16; - let start = 0; - let mut tester = setup_wide(width, start); - - let slot = start + width; - let all = tester.bitfield.get_all(); - // higher than max range by 1 - tester.insert(slot); - let bitfield = tester.bitfield; - for slot in all { - assert!(bitfield.contains(&slot)); - } - assert_eq!(bitfield.excess.len(), 1); - assert_eq!(bitfield.count, 3); - } - - #[test] - fn test_bitfield_insert_wide_before() { - solana_logger::setup(); - let width = 16; - let start = 100; - let mut bitfield = setup_wide(width, start).bitfield; - - let slot = start + 1 - width; - // assert here - would make min too low, causing too wide of a range - bitfield.insert(slot); - assert_eq!(1, bitfield.excess.len()); - assert_eq!(3, bitfield.count); - assert!(bitfield.contains(&slot)); - } - - #[test] - fn test_bitfield_insert_wide_before_ok() { - solana_logger::setup(); - let width = 16; - let start = 100; - let mut bitfield = setup_wide(width, start).bitfield; - - let slot = start + 2 - width; // this item would make our width exactly equal to what is allowed, but it is also inserting prior to min - bitfield.insert(slot); - assert_eq!(1, bitfield.excess.len()); - assert!(bitfield.contains(&slot)); - assert_eq!(3, bitfield.count); - } - - #[test] - fn test_bitfield_contains_wide_no_assert() { - { - let width = 16; - let start = 0; - let bitfield = setup_wide(width, start).bitfield; - - let mut slot = width; - assert!(!bitfield.contains(&slot)); - slot += 1; - assert!(!bitfield.contains(&slot)); - } - { - let width = 16; - let start = 100; - let bitfield = setup_wide(width, start).bitfield; - - // too large - let mut slot = width; - assert!(!bitfield.contains(&slot)); - slot += 1; - assert!(!bitfield.contains(&slot)); - // too small, before min - slot = 0; - assert!(!bitfield.contains(&slot)); - } - } - - #[test] - fn test_bitfield_remove_wide() { - let width = 16; - let start = 0; - let mut tester = setup_wide(width, start); - let slot = width; - assert!(!tester.remove(&slot)); - } - - #[test] - fn test_bitfield_excess2() { - solana_logger::setup(); - let width = 16; - let mut tester = setup_empty(width); - let slot = 100; - // insert 1st slot - tester.insert(slot); - assert!(tester.bitfield.excess.is_empty()); - - // insert a slot before the previous one. this is 'excess' since we don't use this pattern in normal operation - let slot2 = slot - 1; - tester.insert(slot2); - assert_eq!(tester.bitfield.excess.len(), 1); - - // remove the 1st slot. we will be left with only excess - tester.remove(&slot); - assert!(tester.bitfield.contains(&slot2)); - assert_eq!(tester.bitfield.excess.len(), 1); - - // re-insert at valid range, making sure we don't insert into excess - tester.insert(slot); - assert_eq!(tester.bitfield.excess.len(), 1); - - // remove the excess slot. - tester.remove(&slot2); - assert!(tester.bitfield.contains(&slot)); - assert!(tester.bitfield.excess.is_empty()); - - // re-insert the excess slot - tester.insert(slot2); - assert_eq!(tester.bitfield.excess.len(), 1); - } - - #[test] - fn test_bitfield_excess() { - solana_logger::setup(); - // start at slot 0 or a separate, higher slot - for width in [16, 4194304].iter() { - let width = *width; - let mut tester = setup_empty(width); - for start in [0, width * 5].iter().cloned() { - // recreate means create empty bitfield with each iteration, otherwise re-use - for recreate in [false, true].iter().cloned() { - let max = start + 3; - // first root to add - for slot in start..max { - // subsequent roots to add - for slot2 in (slot + 1)..max { - // reverse_slots = 1 means add slots in reverse order (max to min). This causes us to add second and later slots to excess. - for reverse_slots in [false, true].iter().cloned() { - let maybe_reverse = |slot| { - if reverse_slots { - max - slot - } else { - slot - } - }; - if recreate { - let recreated = setup_empty(width); - tester = recreated; - } - - // insert - for slot in slot..=slot2 { - let slot_use = maybe_reverse(slot); - tester.insert(slot_use); - debug!( - "slot: {}, bitfield: {:?}, reverse: {}, len: {}, excess: {:?}", - slot_use, - tester.bitfield, - reverse_slots, - tester.bitfield.len(), - tester.bitfield.excess - ); - assert!( - (reverse_slots && tester.bitfield.len() > 1) - ^ tester.bitfield.excess.is_empty() - ); - } - if start > width * 2 { - assert!(!tester.bitfield.contains(&(start - width * 2))); - } - assert!(!tester.bitfield.contains(&(start + width * 2))); - let len = (slot2 - slot + 1) as usize; - assert_eq!(tester.bitfield.len(), len); - assert_eq!(tester.bitfield.count, len); - - // remove - for slot in slot..=slot2 { - let slot_use = maybe_reverse(slot); - assert!(tester.remove(&slot_use)); - assert!( - (reverse_slots && !tester.bitfield.is_empty()) - ^ tester.bitfield.excess.is_empty() - ); - } - assert!(tester.bitfield.is_empty()); - assert_eq!(tester.bitfield.count, 0); - if start > width * 2 { - assert!(!tester.bitfield.contains(&(start - width * 2))); - } - assert!(!tester.bitfield.contains(&(start + width * 2))); - } - } - } - } - } - } - } - - #[test] - fn test_bitfield_remove_wide_before() { - let width = 16; - let start = 100; - let mut tester = setup_wide(width, start); - let slot = start + 1 - width; - assert!(!tester.remove(&slot)); - } - - fn compare_internal(hashset: &HashSet, bitfield: &RollingBitField) { - assert_eq!(hashset.len(), bitfield.len()); - assert_eq!(hashset.is_empty(), bitfield.is_empty()); - if !bitfield.is_empty() { - let mut min = Slot::MAX; - let mut overall_min = Slot::MAX; - let mut max = Slot::MIN; - for item in bitfield.get_all() { - assert!(hashset.contains(&item)); - if !bitfield.excess.contains(&item) { - min = std::cmp::min(min, item); - max = std::cmp::max(max, item); - } - overall_min = std::cmp::min(overall_min, item); - } - assert_eq!(bitfield.min(), Some(overall_min)); - assert_eq!(bitfield.get_all().len(), hashset.len()); - // range isn't tracked for excess items - if bitfield.excess.len() != bitfield.len() { - let width = if bitfield.is_empty() { - 0 - } else { - max + 1 - min - }; - assert!( - bitfield.range_width() >= width, - "hashset: {:?}, bitfield: {:?}, bitfield.range_width: {}, width: {}", - hashset, - bitfield.get_all(), - bitfield.range_width(), - width, - ); - } - } else { - assert_eq!(bitfield.min(), None); - } - } - - fn compare(hashset: &HashSet, bitfield: &RollingBitField) { - compare_internal(hashset, bitfield); - let clone = bitfield.clone(); - compare_internal(hashset, &clone); - assert!(clone.eq(bitfield)); - assert_eq!(clone, *bitfield); - } - - #[test] - fn test_bitfield_functionality() { - solana_logger::setup(); - - // bitfield sizes are powers of 2, cycle through values of 1, 2, 4, .. 2^9 - for power in 0..10 { - let max_bitfield_width = 2u64.pow(power) as u64; - let width_iteration_max = if max_bitfield_width > 1 { - // add up to 2 items so we can test out multiple items - 3 - } else { - // 0 or 1 items is all we can fit with a width of 1 item - 2 - }; - for width in 0..width_iteration_max { - let mut tester = setup_empty(max_bitfield_width); - - let min = 101_000; - let dead = 19; - - let mut slot = min; - while tester.hash_set.len() < width { - slot += 1; - if max_bitfield_width > 2 && slot % dead == 0 { - // with max_bitfield_width of 1 and 2, there is no room for dead slots - continue; - } - tester.insert(slot); - } - let max = slot + 1; - - for slot in (min - 10)..max + 100 { - assert_eq!( - tester.bitfield.contains(&slot), - tester.hash_set.contains(&slot) - ); - } - - if width > 0 { - assert!(tester.remove(&slot)); - assert!(!tester.remove(&slot)); - } - - let all = tester.bitfield.get_all(); - - // remove the rest, including a call that removes slot again - for item in all.iter() { - assert!(tester.remove(item)); - assert!(!tester.remove(item)); - } - - let min = max + ((width * 2) as u64) + 3; - let slot = min; // several widths past previous min - let max = slot + 1; - tester.insert(slot); - - for slot in (min - 10)..max + 100 { - assert_eq!( - tester.bitfield.contains(&slot), - tester.hash_set.contains(&slot) - ); - } - } - } - } - - fn bitfield_insert_and_test(bitfield: &mut RollingBitField, slot: Slot) { - let len = bitfield.len(); - let old_all = bitfield.get_all(); - let (new_min, new_max) = if bitfield.is_empty() { - (slot, slot + 1) - } else { - ( - std::cmp::min(bitfield.min, slot), - std::cmp::max(bitfield.max_exclusive, slot + 1), - ) - }; - bitfield.insert(slot); - assert_eq!(bitfield.min, new_min); - assert_eq!(bitfield.max_exclusive, new_max); - assert_eq!(bitfield.len(), len + 1); - assert!(!bitfield.is_empty()); - assert!(bitfield.contains(&slot)); - // verify aliasing is what we expect - assert!(bitfield.contains_assume_in_range(&(slot + bitfield.max_width))); - let get_all = bitfield.get_all(); - old_all - .into_iter() - .for_each(|slot| assert!(get_all.contains(&slot))); - assert!(get_all.contains(&slot)); - assert!(get_all.len() == len + 1); - } - - #[test] - fn test_bitfield_clear() { - let mut bitfield = RollingBitField::new(4); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - bitfield_insert_and_test(&mut bitfield, 0); - bitfield.clear(); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - assert!(bitfield.get_all().is_empty()); - bitfield_insert_and_test(&mut bitfield, 1); - bitfield.clear(); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - assert!(bitfield.get_all().is_empty()); - bitfield_insert_and_test(&mut bitfield, 4); - } - - #[test] - fn test_bitfield_wrapping() { - let mut bitfield = RollingBitField::new(4); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - bitfield_insert_and_test(&mut bitfield, 0); - assert_eq!(bitfield.get_all(), vec![0]); - bitfield_insert_and_test(&mut bitfield, 2); - assert_eq!(bitfield.get_all(), vec![0, 2]); - bitfield_insert_and_test(&mut bitfield, 3); - bitfield.insert(3); // redundant insert - assert_eq!(bitfield.get_all(), vec![0, 2, 3]); - assert!(bitfield.remove(&0)); - assert!(!bitfield.remove(&0)); - assert_eq!(bitfield.min, 2); - assert_eq!(bitfield.max_exclusive, 4); - assert_eq!(bitfield.len(), 2); - assert!(!bitfield.remove(&0)); // redundant remove - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.get_all(), vec![2, 3]); - bitfield.insert(4); // wrapped around value - same bit as '0' - assert_eq!(bitfield.min, 2); - assert_eq!(bitfield.max_exclusive, 5); - assert_eq!(bitfield.len(), 3); - assert_eq!(bitfield.get_all(), vec![2, 3, 4]); - assert!(bitfield.remove(&2)); - assert_eq!(bitfield.min, 3); - assert_eq!(bitfield.max_exclusive, 5); - assert_eq!(bitfield.len(), 2); - assert_eq!(bitfield.get_all(), vec![3, 4]); - assert!(bitfield.remove(&3)); - assert_eq!(bitfield.min, 4); - assert_eq!(bitfield.max_exclusive, 5); - assert_eq!(bitfield.len(), 1); - assert_eq!(bitfield.get_all(), vec![4]); - assert!(bitfield.remove(&4)); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - assert!(bitfield.get_all().is_empty()); - bitfield_insert_and_test(&mut bitfield, 8); - assert!(bitfield.remove(&8)); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - assert!(bitfield.get_all().is_empty()); - bitfield_insert_and_test(&mut bitfield, 9); - assert!(bitfield.remove(&9)); - assert_eq!(bitfield.len(), 0); - assert!(bitfield.is_empty()); - assert!(bitfield.get_all().is_empty()); - } - - #[test] - fn test_bitfield_smaller() { - // smaller bitfield, fewer entries, including 0 - solana_logger::setup(); - - for width in 0..34 { - let mut bitfield = RollingBitField::new(4096); - let mut hash_set = HashSet::new(); - - let min = 1_010_000; - let dead = 19; - - let mut slot = min; - while hash_set.len() < width { - slot += 1; - if slot % dead == 0 { - continue; - } - hash_set.insert(slot); - bitfield.insert(slot); - } - - let max = slot + 1; - - let mut time = Measure::start(""); - let mut count = 0; - for slot in (min - 10)..max + 100 { - if hash_set.contains(&slot) { - count += 1; - } - } - time.stop(); - - let mut time2 = Measure::start(""); - let mut count2 = 0; - for slot in (min - 10)..max + 100 { - if bitfield.contains(&slot) { - count2 += 1; - } - } - time2.stop(); - info!( - "{}, {}, {}", - time.as_ms(), - time2.as_ms(), - time.as_ns() / time2.as_ns() - ); - assert_eq!(count, count2); - } - } - const COLLECT_ALL_UNSORTED_FALSE: bool = false; #[test] diff --git a/runtime/src/ancestors.rs b/runtime/src/ancestors.rs index a8c4a5835d..844fc99a50 100644 --- a/runtime/src/ancestors.rs +++ b/runtime/src/ancestors.rs @@ -1,5 +1,5 @@ use { - crate::accounts_index::RollingBitField, + crate::rolling_bit_field::RollingBitField, core::fmt::{Debug, Formatter}, solana_sdk::clock::Slot, std::collections::HashMap, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 40336cd494..01d46007af 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -43,6 +43,7 @@ mod nonce_keyed_account; mod pubkey_bins; mod read_only_accounts_cache; pub mod rent_collector; +mod rolling_bit_field; pub mod secondary_index; pub mod serde_snapshot; mod shared_buffer_reader; diff --git a/runtime/src/rolling_bit_field.rs b/runtime/src/rolling_bit_field.rs new file mode 100644 index 0000000000..4200880db3 --- /dev/null +++ b/runtime/src/rolling_bit_field.rs @@ -0,0 +1,904 @@ +//! functionally similar to a hashset +//! Relies on there being a sliding window of key values. The key values continue to increase. +//! Old key values are removed from the lesser values and do not accumulate. + +use {bv::BitVec, std::collections::HashSet}; + +#[derive(Debug, Default, AbiExample, Clone)] +pub struct RollingBitField { + max_width: u64, + min: u64, + max_exclusive: u64, + bits: BitVec, + count: usize, + // These are items that are true and lower than min. + // They would cause us to exceed max_width if we stored them in our bit field. + // We only expect these items in conditions where there is some other bug in the system + // or in testing when large ranges are created. + excess: HashSet, +} + +impl PartialEq for RollingBitField { + fn eq(&self, other: &Self) -> bool { + // 2 instances could have different internal data for the same values, + // so we have to compare data. + self.len() == other.len() && { + for item in self.get_all() { + if !other.contains(&item) { + return false; + } + } + true + } + } +} + +/// functionally similar to a hashset +/// Relies on there being a sliding window of key values. The key values continue to increase. +/// Old key values are removed from the lesser values and do not accumulate. +impl RollingBitField { + pub fn new(max_width: u64) -> Self { + assert!(max_width > 0); + assert!(max_width.is_power_of_two()); // power of 2 to make dividing a shift + let bits = BitVec::new_fill(false, max_width); + Self { + max_width, + bits, + count: 0, + min: 0, + max_exclusive: 0, + excess: HashSet::new(), + } + } + + // find the array index + fn get_address(&self, key: &u64) -> u64 { + key % self.max_width + } + + pub fn range_width(&self) -> u64 { + // note that max isn't updated on remove, so it can be above the current max + self.max_exclusive - self.min + } + + pub fn min(&self) -> Option { + if self.is_empty() { + None + } else if self.excess.is_empty() { + Some(self.min) + } else { + let mut min = if self.all_items_in_excess() { + u64::MAX + } else { + self.min + }; + for item in &self.excess { + min = std::cmp::min(min, *item); + } + Some(min) + } + } + + pub fn insert(&mut self, key: u64) { + let mut bits_empty = self.count == 0 || self.all_items_in_excess(); + let update_bits = if bits_empty { + true // nothing in bits, so in range + } else if key < self.min { + // bits not empty and this insert is before min, so add to excess + if self.excess.insert(key) { + self.count += 1; + } + false + } else if key < self.max_exclusive { + true // fits current bit field range + } else { + // key is >= max + let new_max = key + 1; + loop { + let new_width = new_max.saturating_sub(self.min); + if new_width <= self.max_width { + // this key will fit the max range + break; + } + + // move the min item from bits to excess and then purge from min to make room for this new max + let inserted = self.excess.insert(self.min); + assert!(inserted); + + let key = self.min; + let address = self.get_address(&key); + self.bits.set(address, false); + self.purge(&key); + + if self.all_items_in_excess() { + // if we moved the last existing item to excess, then we are ready to insert the new item in the bits + bits_empty = true; + break; + } + } + + true // moved things to excess if necessary, so update bits with the new entry + }; + + if update_bits { + let address = self.get_address(&key); + let value = self.bits.get(address); + if !value { + self.bits.set(address, true); + if bits_empty { + self.min = key; + self.max_exclusive = key + 1; + } else { + self.min = std::cmp::min(self.min, key); + self.max_exclusive = std::cmp::max(self.max_exclusive, key + 1); + assert!( + self.min + self.max_width >= self.max_exclusive, + "min: {}, max: {}, max_width: {}", + self.min, + self.max_exclusive, + self.max_width + ); + } + self.count += 1; + } + } + } + + /// remove key from set, return if item was in the set + pub fn remove(&mut self, key: &u64) -> bool { + if key >= &self.min { + // if asked to remove something bigger than max, then no-op + if key < &self.max_exclusive { + let address = self.get_address(key); + let get = self.bits.get(address); + if get { + self.count -= 1; + self.bits.set(address, false); + self.purge(key); + } + get + } else { + false + } + } else { + // asked to remove something < min. would be in excess if it exists + let remove = self.excess.remove(key); + if remove { + self.count -= 1; + } + remove + } + } + + fn all_items_in_excess(&self) -> bool { + self.excess.len() == self.count + } + + // after removing 'key' where 'key' = min, make min the correct new min value + fn purge(&mut self, key: &u64) { + if self.count > 0 && !self.all_items_in_excess() { + if key == &self.min { + let start = self.min + 1; // min just got removed + for key in start..self.max_exclusive { + if self.contains_assume_in_range(&key) { + self.min = key; + break; + } + } + } + } else { + // The idea is that there are no items in the bitfield anymore. + // But, there MAY be items in excess. The model works such that items < min go into excess. + // So, after purging all items from bitfield, we hold max to be what it previously was, but set min to max. + // Thus, if we lookup >= max, answer is always false without having to look in excess. + // If we changed max here to 0, we would lose the ability to know the range of items in excess (if any). + // So, now, with min updated = max: + // If we lookup < max, then we first check min. + // If >= min, then we look in bitfield. + // Otherwise, we look in excess since the request is < min. + // So, resetting min like this after a remove results in the correct behavior for the model. + // Later, if we insert and there are 0 items total (excess + bitfield), then we reset min/max to reflect the new item only. + self.min = self.max_exclusive; + } + } + + fn contains_assume_in_range(&self, key: &u64) -> bool { + // the result may be aliased. Caller is responsible for determining key is in range. + let address = self.get_address(key); + self.bits.get(address) + } + + // This is the 99% use case. + // This needs be fast for the most common case of asking for key >= min. + pub fn contains(&self, key: &u64) -> bool { + if key < &self.max_exclusive { + if key >= &self.min { + // in the bitfield range + self.contains_assume_in_range(key) + } else { + self.excess.contains(key) + } + } else { + false + } + } + + pub fn len(&self) -> usize { + self.count + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn max_exclusive(&self) -> u64 { + self.max_exclusive + } + + pub fn max_inclusive(&self) -> u64 { + self.max_exclusive.saturating_sub(1) + } + + pub fn get_all(&self) -> Vec { + let mut all = Vec::with_capacity(self.count); + self.excess.iter().for_each(|slot| all.push(*slot)); + for key in self.min..self.max_exclusive { + if self.contains_assume_in_range(&key) { + all.push(key); + } + } + all + } +} + +#[cfg(test)] +pub mod tests { + use {super::*, log::*, solana_measure::measure::Measure, solana_sdk::clock::Slot}; + + impl RollingBitField { + pub fn clear(&mut self) { + let mut n = Self::new(self.max_width); + std::mem::swap(&mut n, self); + } + } + + #[test] + fn test_bitfield_delete_non_excess() { + solana_logger::setup(); + let len = 16; + let mut bitfield = RollingBitField::new(len); + assert_eq!(bitfield.min(), None); + + bitfield.insert(0); + assert_eq!(bitfield.min(), Some(0)); + let too_big = len + 1; + bitfield.insert(too_big); + assert!(bitfield.contains(&0)); + assert!(bitfield.contains(&too_big)); + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.excess.len(), 1); + assert_eq!(bitfield.min, too_big); + assert_eq!(bitfield.min(), Some(0)); + assert_eq!(bitfield.max_exclusive, too_big + 1); + + // delete the thing that is NOT in excess + bitfield.remove(&too_big); + assert_eq!(bitfield.min, too_big + 1); + assert_eq!(bitfield.max_exclusive, too_big + 1); + let too_big_times_2 = too_big * 2; + bitfield.insert(too_big_times_2); + assert!(bitfield.contains(&0)); + assert!(bitfield.contains(&too_big_times_2)); + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.excess.len(), 1); + assert_eq!(bitfield.min(), bitfield.excess.iter().min().copied()); + assert_eq!(bitfield.min, too_big_times_2); + assert_eq!(bitfield.max_exclusive, too_big_times_2 + 1); + + bitfield.remove(&0); + bitfield.remove(&too_big_times_2); + assert!(bitfield.is_empty()); + let other = 5; + bitfield.insert(other); + assert!(bitfield.contains(&other)); + assert!(bitfield.excess.is_empty()); + assert_eq!(bitfield.min, other); + assert_eq!(bitfield.max_exclusive, other + 1); + } + + #[test] + fn test_bitfield_insert_excess() { + solana_logger::setup(); + let len = 16; + let mut bitfield = RollingBitField::new(len); + + bitfield.insert(0); + let too_big = len + 1; + bitfield.insert(too_big); + assert!(bitfield.contains(&0)); + assert!(bitfield.contains(&too_big)); + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.excess.len(), 1); + assert!(bitfield.excess.contains(&0)); + assert_eq!(bitfield.min, too_big); + assert_eq!(bitfield.max_exclusive, too_big + 1); + + // delete the thing that IS in excess + // this does NOT affect min/max + bitfield.remove(&0); + assert_eq!(bitfield.min, too_big); + assert_eq!(bitfield.max_exclusive, too_big + 1); + // re-add to excess + bitfield.insert(0); + assert!(bitfield.contains(&0)); + assert!(bitfield.contains(&too_big)); + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.excess.len(), 1); + assert_eq!(bitfield.min, too_big); + assert_eq!(bitfield.max_exclusive, too_big + 1); + } + + #[test] + fn test_bitfield_permutations() { + solana_logger::setup(); + let mut bitfield = RollingBitField::new(2097152); + let mut hash = HashSet::new(); + + let min = 101_000; + let width = 400_000; + let dead = 19; + + let mut slot = min; + while hash.len() < width { + slot += 1; + if slot % dead == 0 { + continue; + } + hash.insert(slot); + bitfield.insert(slot); + } + compare(&hash, &bitfield); + + let max = slot + 1; + + let mut time = Measure::start(""); + let mut count = 0; + for slot in (min - 10)..max + 100 { + if hash.contains(&slot) { + count += 1; + } + } + time.stop(); + + let mut time2 = Measure::start(""); + let mut count2 = 0; + for slot in (min - 10)..max + 100 { + if bitfield.contains(&slot) { + count2 += 1; + } + } + time2.stop(); + info!( + "{}ms, {}ms, {} ratio", + time.as_ms(), + time2.as_ms(), + time.as_ns() / time2.as_ns() + ); + assert_eq!(count, count2); + } + + #[test] + #[should_panic(expected = "assertion failed: max_width.is_power_of_two()")] + fn test_bitfield_power_2() { + let _ = RollingBitField::new(3); + } + + #[test] + #[should_panic(expected = "assertion failed: max_width > 0")] + fn test_bitfield_0() { + let _ = RollingBitField::new(0); + } + + fn setup_empty(width: u64) -> RollingBitFieldTester { + let bitfield = RollingBitField::new(width); + let hash_set = HashSet::new(); + RollingBitFieldTester { bitfield, hash_set } + } + + struct RollingBitFieldTester { + pub bitfield: RollingBitField, + pub hash_set: HashSet, + } + + impl RollingBitFieldTester { + fn insert(&mut self, slot: u64) { + self.bitfield.insert(slot); + self.hash_set.insert(slot); + assert!(self.bitfield.contains(&slot)); + compare(&self.hash_set, &self.bitfield); + } + fn remove(&mut self, slot: &u64) -> bool { + let result = self.bitfield.remove(slot); + assert_eq!(result, self.hash_set.remove(slot)); + assert!(!self.bitfield.contains(slot)); + self.compare(); + result + } + fn compare(&self) { + compare(&self.hash_set, &self.bitfield); + } + } + + fn setup_wide(width: u64, start: u64) -> RollingBitFieldTester { + let mut tester = setup_empty(width); + + tester.compare(); + tester.insert(start); + tester.insert(start + 1); + tester + } + + #[test] + fn test_bitfield_insert_wide() { + solana_logger::setup(); + let width = 16; + let start = 0; + let mut tester = setup_wide(width, start); + + let slot = start + width; + let all = tester.bitfield.get_all(); + // higher than max range by 1 + tester.insert(slot); + let bitfield = tester.bitfield; + for slot in all { + assert!(bitfield.contains(&slot)); + } + assert_eq!(bitfield.excess.len(), 1); + assert_eq!(bitfield.count, 3); + } + + #[test] + fn test_bitfield_insert_wide_before() { + solana_logger::setup(); + let width = 16; + let start = 100; + let mut bitfield = setup_wide(width, start).bitfield; + + let slot = start + 1 - width; + // assert here - would make min too low, causing too wide of a range + bitfield.insert(slot); + assert_eq!(1, bitfield.excess.len()); + assert_eq!(3, bitfield.count); + assert!(bitfield.contains(&slot)); + } + + #[test] + fn test_bitfield_insert_wide_before_ok() { + solana_logger::setup(); + let width = 16; + let start = 100; + let mut bitfield = setup_wide(width, start).bitfield; + + let slot = start + 2 - width; // this item would make our width exactly equal to what is allowed, but it is also inserting prior to min + bitfield.insert(slot); + assert_eq!(1, bitfield.excess.len()); + assert!(bitfield.contains(&slot)); + assert_eq!(3, bitfield.count); + } + + #[test] + fn test_bitfield_contains_wide_no_assert() { + { + let width = 16; + let start = 0; + let bitfield = setup_wide(width, start).bitfield; + + let mut slot = width; + assert!(!bitfield.contains(&slot)); + slot += 1; + assert!(!bitfield.contains(&slot)); + } + { + let width = 16; + let start = 100; + let bitfield = setup_wide(width, start).bitfield; + + // too large + let mut slot = width; + assert!(!bitfield.contains(&slot)); + slot += 1; + assert!(!bitfield.contains(&slot)); + // too small, before min + slot = 0; + assert!(!bitfield.contains(&slot)); + } + } + + #[test] + fn test_bitfield_remove_wide() { + let width = 16; + let start = 0; + let mut tester = setup_wide(width, start); + let slot = width; + assert!(!tester.remove(&slot)); + } + + #[test] + fn test_bitfield_excess2() { + solana_logger::setup(); + let width = 16; + let mut tester = setup_empty(width); + let slot = 100; + // insert 1st slot + tester.insert(slot); + assert!(tester.bitfield.excess.is_empty()); + + // insert a slot before the previous one. this is 'excess' since we don't use this pattern in normal operation + let slot2 = slot - 1; + tester.insert(slot2); + assert_eq!(tester.bitfield.excess.len(), 1); + + // remove the 1st slot. we will be left with only excess + tester.remove(&slot); + assert!(tester.bitfield.contains(&slot2)); + assert_eq!(tester.bitfield.excess.len(), 1); + + // re-insert at valid range, making sure we don't insert into excess + tester.insert(slot); + assert_eq!(tester.bitfield.excess.len(), 1); + + // remove the excess slot. + tester.remove(&slot2); + assert!(tester.bitfield.contains(&slot)); + assert!(tester.bitfield.excess.is_empty()); + + // re-insert the excess slot + tester.insert(slot2); + assert_eq!(tester.bitfield.excess.len(), 1); + } + + #[test] + fn test_bitfield_excess() { + solana_logger::setup(); + // start at slot 0 or a separate, higher slot + for width in [16, 4194304].iter() { + let width = *width; + let mut tester = setup_empty(width); + for start in [0, width * 5].iter().cloned() { + // recreate means create empty bitfield with each iteration, otherwise re-use + for recreate in [false, true].iter().cloned() { + let max = start + 3; + // first root to add + for slot in start..max { + // subsequent roots to add + for slot2 in (slot + 1)..max { + // reverse_slots = 1 means add slots in reverse order (max to min). This causes us to add second and later slots to excess. + for reverse_slots in [false, true].iter().cloned() { + let maybe_reverse = |slot| { + if reverse_slots { + max - slot + } else { + slot + } + }; + if recreate { + let recreated = setup_empty(width); + tester = recreated; + } + + // insert + for slot in slot..=slot2 { + let slot_use = maybe_reverse(slot); + tester.insert(slot_use); + debug!( + "slot: {}, bitfield: {:?}, reverse: {}, len: {}, excess: {:?}", + slot_use, + tester.bitfield, + reverse_slots, + tester.bitfield.len(), + tester.bitfield.excess + ); + assert!( + (reverse_slots && tester.bitfield.len() > 1) + ^ tester.bitfield.excess.is_empty() + ); + } + if start > width * 2 { + assert!(!tester.bitfield.contains(&(start - width * 2))); + } + assert!(!tester.bitfield.contains(&(start + width * 2))); + let len = (slot2 - slot + 1) as usize; + assert_eq!(tester.bitfield.len(), len); + assert_eq!(tester.bitfield.count, len); + + // remove + for slot in slot..=slot2 { + let slot_use = maybe_reverse(slot); + assert!(tester.remove(&slot_use)); + assert!( + (reverse_slots && !tester.bitfield.is_empty()) + ^ tester.bitfield.excess.is_empty() + ); + } + assert!(tester.bitfield.is_empty()); + assert_eq!(tester.bitfield.count, 0); + if start > width * 2 { + assert!(!tester.bitfield.contains(&(start - width * 2))); + } + assert!(!tester.bitfield.contains(&(start + width * 2))); + } + } + } + } + } + } + } + + #[test] + fn test_bitfield_remove_wide_before() { + let width = 16; + let start = 100; + let mut tester = setup_wide(width, start); + let slot = start + 1 - width; + assert!(!tester.remove(&slot)); + } + + fn compare_internal(hashset: &HashSet, bitfield: &RollingBitField) { + assert_eq!(hashset.len(), bitfield.len()); + assert_eq!(hashset.is_empty(), bitfield.is_empty()); + if !bitfield.is_empty() { + let mut min = Slot::MAX; + let mut overall_min = Slot::MAX; + let mut max = Slot::MIN; + for item in bitfield.get_all() { + assert!(hashset.contains(&item)); + if !bitfield.excess.contains(&item) { + min = std::cmp::min(min, item); + max = std::cmp::max(max, item); + } + overall_min = std::cmp::min(overall_min, item); + } + assert_eq!(bitfield.min(), Some(overall_min)); + assert_eq!(bitfield.get_all().len(), hashset.len()); + // range isn't tracked for excess items + if bitfield.excess.len() != bitfield.len() { + let width = if bitfield.is_empty() { + 0 + } else { + max + 1 - min + }; + assert!( + bitfield.range_width() >= width, + "hashset: {:?}, bitfield: {:?}, bitfield.range_width: {}, width: {}", + hashset, + bitfield.get_all(), + bitfield.range_width(), + width, + ); + } + } else { + assert_eq!(bitfield.min(), None); + } + } + + fn compare(hashset: &HashSet, bitfield: &RollingBitField) { + compare_internal(hashset, bitfield); + let clone = bitfield.clone(); + compare_internal(hashset, &clone); + assert!(clone.eq(bitfield)); + assert_eq!(clone, *bitfield); + } + + #[test] + fn test_bitfield_functionality() { + solana_logger::setup(); + + // bitfield sizes are powers of 2, cycle through values of 1, 2, 4, .. 2^9 + for power in 0..10 { + let max_bitfield_width = 2u64.pow(power) as u64; + let width_iteration_max = if max_bitfield_width > 1 { + // add up to 2 items so we can test out multiple items + 3 + } else { + // 0 or 1 items is all we can fit with a width of 1 item + 2 + }; + for width in 0..width_iteration_max { + let mut tester = setup_empty(max_bitfield_width); + + let min = 101_000; + let dead = 19; + + let mut slot = min; + while tester.hash_set.len() < width { + slot += 1; + if max_bitfield_width > 2 && slot % dead == 0 { + // with max_bitfield_width of 1 and 2, there is no room for dead slots + continue; + } + tester.insert(slot); + } + let max = slot + 1; + + for slot in (min - 10)..max + 100 { + assert_eq!( + tester.bitfield.contains(&slot), + tester.hash_set.contains(&slot) + ); + } + + if width > 0 { + assert!(tester.remove(&slot)); + assert!(!tester.remove(&slot)); + } + + let all = tester.bitfield.get_all(); + + // remove the rest, including a call that removes slot again + for item in all.iter() { + assert!(tester.remove(item)); + assert!(!tester.remove(item)); + } + + let min = max + ((width * 2) as u64) + 3; + let slot = min; // several widths past previous min + let max = slot + 1; + tester.insert(slot); + + for slot in (min - 10)..max + 100 { + assert_eq!( + tester.bitfield.contains(&slot), + tester.hash_set.contains(&slot) + ); + } + } + } + } + + fn bitfield_insert_and_test(bitfield: &mut RollingBitField, slot: Slot) { + let len = bitfield.len(); + let old_all = bitfield.get_all(); + let (new_min, new_max) = if bitfield.is_empty() { + (slot, slot + 1) + } else { + ( + std::cmp::min(bitfield.min, slot), + std::cmp::max(bitfield.max_exclusive, slot + 1), + ) + }; + bitfield.insert(slot); + assert_eq!(bitfield.min, new_min); + assert_eq!(bitfield.max_exclusive, new_max); + assert_eq!(bitfield.len(), len + 1); + assert!(!bitfield.is_empty()); + assert!(bitfield.contains(&slot)); + // verify aliasing is what we expect + assert!(bitfield.contains_assume_in_range(&(slot + bitfield.max_width))); + let get_all = bitfield.get_all(); + old_all + .into_iter() + .for_each(|slot| assert!(get_all.contains(&slot))); + assert!(get_all.contains(&slot)); + assert!(get_all.len() == len + 1); + } + + #[test] + fn test_bitfield_clear() { + let mut bitfield = RollingBitField::new(4); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + bitfield_insert_and_test(&mut bitfield, 0); + bitfield.clear(); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + assert!(bitfield.get_all().is_empty()); + bitfield_insert_and_test(&mut bitfield, 1); + bitfield.clear(); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + assert!(bitfield.get_all().is_empty()); + bitfield_insert_and_test(&mut bitfield, 4); + } + + #[test] + fn test_bitfield_wrapping() { + let mut bitfield = RollingBitField::new(4); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + bitfield_insert_and_test(&mut bitfield, 0); + assert_eq!(bitfield.get_all(), vec![0]); + bitfield_insert_and_test(&mut bitfield, 2); + assert_eq!(bitfield.get_all(), vec![0, 2]); + bitfield_insert_and_test(&mut bitfield, 3); + bitfield.insert(3); // redundant insert + assert_eq!(bitfield.get_all(), vec![0, 2, 3]); + assert!(bitfield.remove(&0)); + assert!(!bitfield.remove(&0)); + assert_eq!(bitfield.min, 2); + assert_eq!(bitfield.max_exclusive, 4); + assert_eq!(bitfield.len(), 2); + assert!(!bitfield.remove(&0)); // redundant remove + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.get_all(), vec![2, 3]); + bitfield.insert(4); // wrapped around value - same bit as '0' + assert_eq!(bitfield.min, 2); + assert_eq!(bitfield.max_exclusive, 5); + assert_eq!(bitfield.len(), 3); + assert_eq!(bitfield.get_all(), vec![2, 3, 4]); + assert!(bitfield.remove(&2)); + assert_eq!(bitfield.min, 3); + assert_eq!(bitfield.max_exclusive, 5); + assert_eq!(bitfield.len(), 2); + assert_eq!(bitfield.get_all(), vec![3, 4]); + assert!(bitfield.remove(&3)); + assert_eq!(bitfield.min, 4); + assert_eq!(bitfield.max_exclusive, 5); + assert_eq!(bitfield.len(), 1); + assert_eq!(bitfield.get_all(), vec![4]); + assert!(bitfield.remove(&4)); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + assert!(bitfield.get_all().is_empty()); + bitfield_insert_and_test(&mut bitfield, 8); + assert!(bitfield.remove(&8)); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + assert!(bitfield.get_all().is_empty()); + bitfield_insert_and_test(&mut bitfield, 9); + assert!(bitfield.remove(&9)); + assert_eq!(bitfield.len(), 0); + assert!(bitfield.is_empty()); + assert!(bitfield.get_all().is_empty()); + } + + #[test] + fn test_bitfield_smaller() { + // smaller bitfield, fewer entries, including 0 + solana_logger::setup(); + + for width in 0..34 { + let mut bitfield = RollingBitField::new(4096); + let mut hash_set = HashSet::new(); + + let min = 1_010_000; + let dead = 19; + + let mut slot = min; + while hash_set.len() < width { + slot += 1; + if slot % dead == 0 { + continue; + } + hash_set.insert(slot); + bitfield.insert(slot); + } + + let max = slot + 1; + + let mut time = Measure::start(""); + let mut count = 0; + for slot in (min - 10)..max + 100 { + if hash_set.contains(&slot) { + count += 1; + } + } + time.stop(); + + let mut time2 = Measure::start(""); + let mut count2 = 0; + for slot in (min - 10)..max + 100 { + if bitfield.contains(&slot) { + count2 += 1; + } + } + time2.stop(); + info!( + "{}, {}, {}", + time.as_ms(), + time2.as_ms(), + time.as_ns() / time2.as_ns() + ); + assert_eq!(count, count2); + } + } +}