From f67749f6e225290f7d7f1ec45a52217a34da2185 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Fri, 31 May 2024 16:43:51 +0200 Subject: [PATCH 1/9] Remove A: Allocator generics bound from BitOr and BitAnd impl BitXor and Sub already don't have them, and its not like we guarantee returning a set with the same A type anyway. I would be fine with adding it to BitXor and Sub instead, but I think it should be consistent. --- src/set.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/set.rs b/src/set.rs index bd6678833..6dc5c2f2d 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1406,11 +1406,10 @@ where } } -impl BitOr<&HashSet> for &HashSet +impl BitOr<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, { type Output = HashSet; @@ -1434,16 +1433,15 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitor(self, rhs: &HashSet) -> HashSet { + fn bitor(self, rhs: &HashSet) -> HashSet { self.union(rhs).cloned().collect() } } -impl BitAnd<&HashSet> for &HashSet +impl BitAnd<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, { type Output = HashSet; @@ -1467,7 +1465,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitand(self, rhs: &HashSet) -> HashSet { + fn bitand(self, rhs: &HashSet) -> HashSet { self.intersection(rhs).cloned().collect() } } From caea18d9dacd30e296d56a4941fa27f680551a70 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Fri, 31 May 2024 16:48:17 +0200 Subject: [PATCH 2/9] Implement Add<&HashSet> for &HashSet This is functionally the same as BitOr, but imo its more intuitive if it exists because of the Sub impl --- src/set.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/set.rs b/src/set.rs index 6dc5c2f2d..cbf96ea30 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::{Add, BitAnd, BitOr, BitXor, Sub}; use super::map::{self, DefaultHashBuilder, HashMap, Keys}; use crate::raw::{Allocator, Global, RawExtractIf}; @@ -1502,6 +1502,38 @@ where } } +impl Add<&HashSet> for &HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + type Output = HashSet; + + /// Returns the union of `self` and `rhs` as a new `HashSet`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); + /// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect(); + /// + /// let set = &a + &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 3, 4]; + /// for x in &set { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn add(self, rhs: &HashSet) -> HashSet { + self.union(rhs).cloned().collect() + } +} + impl Sub<&HashSet> for &HashSet where T: Eq + Hash + Clone, From 6e0aec20066c3c07159e853e6ae6c10554aa1f11 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Fri, 31 May 2024 18:02:46 +0200 Subject: [PATCH 3/9] Implement XxxAssign operations for HashSets Also add a set of benchmarks for set operations --- benches/set_ops.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++ src/set.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 benches/set_ops.rs diff --git a/benches/set_ops.rs b/benches/set_ops.rs new file mode 100644 index 000000000..1908fbb09 --- /dev/null +++ b/benches/set_ops.rs @@ -0,0 +1,167 @@ +//! 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; + +#[bench] +fn set_ops_bit_or(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &large_set | &small_set) +} + +#[bench] +fn set_ops_bit_and(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &large_set & &small_set) +} + +#[bench] +fn set_ops_bit_xor(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &large_set ^ &small_set) +} + +#[bench] +fn set_ops_add(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &large_set + &small_set) +} + +#[bench] +fn set_ops_sub_large_small(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &large_set - &small_set) +} + +#[bench] +fn set_ops_sub_small_large(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| &small_set - &large_set) +} + +#[bench] +fn set_ops_bit_or_assign(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| { + let mut set = large_set.clone(); + set ^= &small_set; + set + }); +} + +#[bench] +fn set_ops_add_assign(b: &mut Bencher) { + let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); + let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) + ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) + .map(|nr| format!("key{}", nr)) + .collect(); + b.iter(|| { + let mut set = small_set.clone(); + set -= &large_set; + set + }); +} diff --git a/src/set.rs b/src/set.rs index cbf96ea30..838226054 100644 --- a/src/set.rs +++ b/src/set.rs @@ -5,7 +5,9 @@ use alloc::borrow::ToOwned; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FusedIterator}; -use core::ops::{Add, BitAnd, BitOr, BitXor, Sub}; +use core::ops::{ + Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign, +}; use super::map::{self, DefaultHashBuilder, HashMap, Keys}; use crate::raw::{Allocator, Global, RawExtractIf}; @@ -1566,6 +1568,170 @@ where } } +impl BitOrAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + /// 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 + Default, +{ + /// 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 + Default, +{ + /// 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 { + if self.contains(item) { + self.remove(item); + } else { + self.insert(item.clone()); + } + } + } +} + +impl AddAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + /// 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![2, 3, 4].into_iter().collect(); + /// + /// a += &b; + /// + /// let mut i = 0; + /// let expected = [1, 2, 3, 4]; + /// for x in &a { + /// assert!(expected.contains(x)); + /// i += 1; + /// } + /// assert_eq!(i, expected.len()); + /// ``` + fn add_assign(&mut self, rhs: &HashSet) { + for item in rhs { + if !self.contains(item) { + self.insert(item.clone()); + } + } + } +} + +impl SubAssign<&HashSet> for HashSet +where + T: Eq + Hash + Clone, + S: BuildHasher + Default, +{ + /// 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) { + self.retain(|item| !rhs.contains(item)); + } +} + /// An iterator over the items of a `HashSet`. /// /// This `struct` is created by the [`iter`] method on [`HashSet`]. From 28982b9f9bc6c69f785d3526745b99208da0f62e Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Sun, 2 Jun 2024 13:17:42 +0200 Subject: [PATCH 4/9] Improve large set - small set performance Also remove S: Default bound on Assign ops --- src/set.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/set.rs b/src/set.rs index 838226054..cbe7a7834 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1571,7 +1571,7 @@ where impl BitOrAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, - S: BuildHasher + Default, + S: BuildHasher, { /// Modifies this set to contain the union of `self` and `rhs`. /// @@ -1605,7 +1605,7 @@ where impl BitAndAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, - S: BuildHasher + Default, + S: BuildHasher, { /// Modifies this set to contain the intersection of `self` and `rhs`. /// @@ -1635,7 +1635,7 @@ where impl BitXorAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, - S: BuildHasher + Default, + S: BuildHasher, { /// Modifies this set to contain the symmetric difference of `self` and `rhs`. /// @@ -1671,7 +1671,7 @@ where impl AddAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, - S: BuildHasher + Default, + S: BuildHasher, { /// Modifies this set to contain the union of `self` and `rhs`. /// @@ -1705,7 +1705,7 @@ where impl SubAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, - S: BuildHasher + Default, + S: BuildHasher, { /// Modifies this set to contain the difference of `self` and `rhs`. /// @@ -1728,7 +1728,13 @@ where /// assert_eq!(i, expected.len()); /// ``` fn sub_assign(&mut self, rhs: &HashSet) { - self.retain(|item| !rhs.contains(item)); + if rhs.len() < self.len() { + for item in rhs { + self.remove(item); + } + } else { + self.retain(|item| !rhs.contains(item)); + } } } From fcb964ba86377ee0d25031aa8afe5bc123c650f4 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Mon, 3 Jun 2024 19:29:16 +0200 Subject: [PATCH 5/9] Minor XOR optimization The performance benefit is honestly within margin of error, so it might be better to undo this in favor of the previous simpler implementation. --- src/set.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/set.rs b/src/set.rs index cbe7a7834..fd28fa959 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1659,11 +1659,15 @@ where /// ``` fn bitxor_assign(&mut self, rhs: &HashSet) { for item in rhs { - if self.contains(item) { - self.remove(item); - } else { - self.insert(item.clone()); - } + 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(), ()); + } + }; } } } From 599bb39d355ab3506bca5922963316cd7321f4b1 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Mon, 17 Jun 2024 20:04:13 +0200 Subject: [PATCH 6/9] Remove HashSet Add and AddAssign implementations --- benches/set_ops.rs | 24 ---------------- src/set.rs | 70 +--------------------------------------------- 2 files changed, 1 insertion(+), 93 deletions(-) diff --git a/benches/set_ops.rs b/benches/set_ops.rs index 1908fbb09..218b313d2 100644 --- a/benches/set_ops.rs +++ b/benches/set_ops.rs @@ -52,16 +52,6 @@ fn set_ops_bit_xor(b: &mut Bencher) { b.iter(|| &large_set ^ &small_set) } -#[bench] -fn set_ops_add(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); - b.iter(|| &large_set + &small_set) -} - #[bench] fn set_ops_sub_large_small(b: &mut Bencher) { let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); @@ -124,20 +114,6 @@ fn set_ops_bit_xor_assign(b: &mut Bencher) { }); } -#[bench] -fn set_ops_add_assign(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); - 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); diff --git a/src/set.rs b/src/set.rs index fd28fa959..8c2585754 100644 --- a/src/set.rs +++ b/src/set.rs @@ -5,9 +5,7 @@ use alloc::borrow::ToOwned; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FusedIterator}; -use core::ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign, -}; +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign}; use super::map::{self, DefaultHashBuilder, HashMap, Keys}; use crate::raw::{Allocator, Global, RawExtractIf}; @@ -1504,38 +1502,6 @@ where } } -impl Add<&HashSet> for &HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the union of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use hashbrown::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect(); - /// - /// let set = &a + &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 3, 4]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn add(self, rhs: &HashSet) -> HashSet { - self.union(rhs).cloned().collect() - } -} - impl Sub<&HashSet> for &HashSet where T: Eq + Hash + Clone, @@ -1672,40 +1638,6 @@ where } } -impl AddAssign<&HashSet> for HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher, -{ - /// 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![2, 3, 4].into_iter().collect(); - /// - /// a += &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 3, 4]; - /// for x in &a { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn add_assign(&mut self, rhs: &HashSet) { - for item in rhs { - if !self.contains(item) { - self.insert(item.clone()); - } - } - } -} - impl SubAssign<&HashSet> for HashSet where T: Eq + Hash + Clone, From 2d289cc9c446207351b9f9735e5d9f87ff7ab3a8 Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Mon, 17 Jun 2024 20:10:29 +0200 Subject: [PATCH 7/9] Move benchmark set creation to separate function To make testing with different elements easier --- benches/set_ops.rs | 105 ++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/benches/set_ops.rs b/benches/set_ops.rs index 218b313d2..4b910a839 100644 --- a/benches/set_ops.rs +++ b/benches/set_ops.rs @@ -22,63 +22,68 @@ const SMALL_SET_SIZE: usize = 100; 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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; @@ -88,11 +93,11 @@ fn set_ops_bit_or_assign(b: &mut Bencher) { #[bench] fn set_ops_bit_and_assign(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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; @@ -102,11 +107,11 @@ fn set_ops_bit_and_assign(b: &mut Bencher) { #[bench] fn set_ops_bit_xor_assign(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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; @@ -116,11 +121,11 @@ fn set_ops_bit_xor_assign(b: &mut Bencher) { #[bench] fn set_ops_sub_assign_large_small(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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; @@ -130,11 +135,11 @@ fn set_ops_sub_assign_large_small(b: &mut Bencher) { #[bench] fn set_ops_sub_assign_small_large(b: &mut Bencher) { - let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect(); - let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP) - ..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP)) - .map(|nr| format!("key{}", nr)) - .collect(); + 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; From 025b2ab667b772710e5891646123f4ec9be34a4a Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Mon, 17 Jun 2024 20:21:36 +0200 Subject: [PATCH 8/9] Add A: Allocator bound to set operations --- src/set.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/set.rs b/src/set.rs index 8c2585754..5e7e81356 100644 --- a/src/set.rs +++ b/src/set.rs @@ -1406,10 +1406,11 @@ where } } -impl BitOr<&HashSet> for &HashSet +impl BitOr<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, + A: Allocator, { type Output = HashSet; @@ -1433,15 +1434,16 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitor(self, rhs: &HashSet) -> HashSet { + fn bitor(self, rhs: &HashSet) -> HashSet { self.union(rhs).cloned().collect() } } -impl BitAnd<&HashSet> for &HashSet +impl BitAnd<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, + A: Allocator, { type Output = HashSet; @@ -1465,15 +1467,16 @@ 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, { type Output = HashSet; @@ -1497,15 +1500,16 @@ 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, { type Output = HashSet; @@ -1529,15 +1533,16 @@ 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 +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`. /// @@ -1559,7 +1564,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitor_assign(&mut self, rhs: &HashSet) { + fn bitor_assign(&mut self, rhs: &HashSet) { for item in rhs { if !self.contains(item) { self.insert(item.clone()); @@ -1568,10 +1573,11 @@ where } } -impl BitAndAssign<&HashSet> for HashSet +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`. /// @@ -1593,15 +1599,16 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitand_assign(&mut self, rhs: &HashSet) { + fn bitand_assign(&mut self, rhs: &HashSet) { self.retain(|item| rhs.contains(item)); } } -impl BitXorAssign<&HashSet> for HashSet +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`. /// @@ -1623,7 +1630,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitxor_assign(&mut self, rhs: &HashSet) { + fn bitxor_assign(&mut self, rhs: &HashSet) { for item in rhs { let entry = self.map.raw_entry_mut().from_key(item); match entry { @@ -1638,10 +1645,11 @@ where } } -impl SubAssign<&HashSet> for HashSet +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`. /// @@ -1663,7 +1671,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn sub_assign(&mut self, rhs: &HashSet) { + fn sub_assign(&mut self, rhs: &HashSet) { if rhs.len() < self.len() { for item in rhs { self.remove(item); From 481ef396bf7c45c66adb42f00590a242085ddb1c Mon Sep 17 00:00:00 2001 From: ToMe25 Date: Mon, 17 Jun 2024 21:55:34 +0200 Subject: [PATCH 9/9] Make non-assigning set ops return a set with the same type of allocator This requires for the allocator to implement Default --- src/set.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/set.rs b/src/set.rs index 5e7e81356..8bb313e7a 100644 --- a/src/set.rs +++ b/src/set.rs @@ -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,7 +1467,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitand(self, rhs: &HashSet) -> HashSet { + fn bitand(self, rhs: &HashSet) -> HashSet { self.intersection(rhs).cloned().collect() } } @@ -1476,9 +1476,9 @@ impl BitXor<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the symmetric difference of `self` and `rhs` as a new `HashSet`. /// @@ -1500,7 +1500,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn bitxor(self, rhs: &HashSet) -> HashSet { + fn bitxor(self, rhs: &HashSet) -> HashSet { self.symmetric_difference(rhs).cloned().collect() } } @@ -1509,9 +1509,9 @@ impl Sub<&HashSet> for &HashSet where T: Eq + Hash + Clone, S: BuildHasher + Default, - A: Allocator, + A: Allocator + Default, { - type Output = HashSet; + type Output = HashSet; /// Returns the difference of `self` and `rhs` as a new `HashSet`. /// @@ -1533,7 +1533,7 @@ where /// } /// assert_eq!(i, expected.len()); /// ``` - fn sub(self, rhs: &HashSet) -> HashSet { + fn sub(self, rhs: &HashSet) -> HashSet { self.difference(rhs).cloned().collect() } }