diff --git a/benches/set_ops.rs b/benches/set_ops.rs new file mode 100644 index 0000000000..4b910a8391 --- /dev/null +++ b/benches/set_ops.rs @@ -0,0 +1,148 @@ +//! This file contains benchmarks for the ops traits implemented by HashSet. +//! Each test is intended to have a defined larger and smaller set, +//! but using a larger size for the "small" set works just as well. +//! +//! Each assigning test is done in the configuration that is faster. Cheating, I know. +//! The exception to this is Sub, because there the result differs. So I made two benchmarks for Sub. + +#![feature(test)] + +extern crate test; + +use hashbrown::HashSet; +use test::Bencher; + +/// The number of items to generate for the larger of the sets. +const LARGE_SET_SIZE: usize = 1000; + +/// The number of items to generate for the smaller of the sets. +const SMALL_SET_SIZE: usize = 100; + +/// The number of keys present in both sets. +const OVERLAPP: usize = + [LARGE_SET_SIZE, SMALL_SET_SIZE][(LARGE_SET_SIZE < SMALL_SET_SIZE) as usize] / 2; + +/// Creates a set containing end - start unique string elements. +fn create_set(start: usize, end: usize) -> HashSet { + (start..end).map(|nr| format!("key{}", nr)).collect() +} + +#[bench] +fn set_ops_bit_or(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| &large_set | &small_set) +} + +#[bench] +fn set_ops_bit_and(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| &large_set & &small_set) +} + +#[bench] +fn set_ops_bit_xor(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| &large_set ^ &small_set) +} + +#[bench] +fn set_ops_sub_large_small(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| &large_set - &small_set) +} + +#[bench] +fn set_ops_sub_small_large(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| &small_set - &large_set) +} + +#[bench] +fn set_ops_bit_or_assign(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| { + let mut set = large_set.clone(); + set |= &small_set; + set + }); +} + +#[bench] +fn set_ops_bit_and_assign(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| { + let mut set = small_set.clone(); + set &= &large_set; + set + }); +} + +#[bench] +fn set_ops_bit_xor_assign(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| { + let mut set = large_set.clone(); + set ^= &small_set; + set + }); +} + +#[bench] +fn set_ops_sub_assign_large_small(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| { + let mut set = large_set.clone(); + set -= &small_set; + set + }); +} + +#[bench] +fn set_ops_sub_assign_small_large(b: &mut Bencher) { + let large_set = create_set(0, LARGE_SET_SIZE); + let small_set = create_set( + LARGE_SET_SIZE - OVERLAPP, + LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP, + ); + b.iter(|| { + let mut set = small_set.clone(); + set -= &large_set; + set + }); +} diff --git a/src/set.rs b/src/set.rs index bd66788336..8bb313e7ad 100644 --- a/src/set.rs +++ b/src/set.rs @@ -5,7 +5,7 @@ use alloc::borrow::ToOwned; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FusedIterator}; -use core::ops::{BitAnd, BitOr, BitXor, Sub}; +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign}; use super::map::{self, DefaultHashBuilder, HashMap, Keys}; use crate::raw::{Allocator, Global, RawExtractIf}; @@ -1410,9 +1410,9 @@ impl BitOr<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the union of `self` and `rhs` as a new `HashSet`. /// @@ -1434,7 +1434,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitor(self, rhs: &HashSet) -> HashSet { + fn bitor(self, rhs: &HashSet) -> HashSet { self.union(rhs).cloned().collect() } } @@ -1443,9 +1443,9 @@ impl BitAnd<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the intersection of `self` and `rhs` as a new `HashSet`. /// @@ -1467,17 +1467,18 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitand(self, rhs: &HashSet) -> HashSet { + fn bitand(self, rhs: &HashSet) -> HashSet { self.intersection(rhs).cloned().collect() } } -impl BitXor<&HashSet> for &HashSet +impl BitXor<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the symmetric difference of `self` and `rhs` as a new `HashSet`. /// @@ -1499,17 +1500,18 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitxor(self, rhs: &HashSet) -> HashSet { + fn bitxor(self, rhs: &HashSet) -> HashSet { self.symmetric_difference(rhs).cloned().collect() } } -impl Sub<&HashSet> for &HashSet +impl Sub<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the difference of `self` and `rhs` as a new `HashSet`. /// @@ -1531,11 +1533,155 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn sub(self, rhs: &HashSet) -> HashSet { + fn sub(self, rhs: &HashSet) -> HashSet { self.difference(rhs).cloned().collect() } } +impl BitOrAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher, + A: Allocator, +{ + /// Modifies this set to contain the union of `self` and `rhs`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// a |= &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 3, 4, 5]; + /// for x in &a { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitor_assign(&mut self, rhs: &HashSet) { + for item in rhs { + if !self.contains(item) { + self.insert(item.clone()); + } + } + } +} + +impl BitAndAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher, + A: Allocator, +{ + /// Modifies this set to contain the intersection of `self` and `rhs`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect(); + /// + /// a &= &b; + /// + /// let mut i = 0; + /// let expected = [2, 3]; + /// for x in &a { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitand_assign(&mut self, rhs: &HashSet) { + self.retain(|item| rhs.contains(item)); + } +} + +impl BitXorAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher, + A: Allocator, +{ + /// Modifies this set to contain the symmetric difference of `self` and `rhs`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// a ^= &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 4, 5]; + /// for x in &a { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn bitxor_assign(&mut self, rhs: &HashSet) { + for item in rhs { + let entry = self.map.raw_entry_mut().from_key(item); + match entry { + map::RawEntryMut::Occupied(e) => { + e.remove(); + } + map::RawEntryMut::Vacant(e) => { + e.insert(item.to_owned(), ()); + } + }; + } + } +} + +impl SubAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher, + A: Allocator, +{ + /// Modifies this set to contain the difference of `self` and `rhs`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); + /// + /// a -= &b; + /// + /// let mut i = 0; + /// let expected = [1, 2]; + /// for x in &a { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn sub_assign(&mut self, rhs: &HashSet) { + if rhs.len() < self.len() { + for item in rhs { + self.remove(item); + } + } else { + self.retain(|item| !rhs.contains(item)); + } + } +} + /// An iterator over the items of a `HashSet`. /// /// This `struct` is created by the [`iter`] method on [`HashSet`].