Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve performance of recursion guard #1156

Merged
merged 7 commits into from Jan 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 13 additions & 15 deletions src/recursion_guard.rs
Expand Up @@ -95,29 +95,26 @@ impl<T: Eq + Hash + Clone> SmallContainer<T> {
pub fn contains_or_insert(&mut self, v: T) -> Option<usize> {
match self {
Self::Array(array) => {
let mut first_slot: Option<usize> = None;
for (index, op_value) in array.iter().enumerate() {
for (index, op_value) in array.iter_mut().enumerate() {
if let Some(existing) = op_value {
if existing == &v {
return None;
}
} else {
first_slot = first_slot.or(Some(index));
*op_value = Some(v);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this is safe because there's a case where an earlier item has been removed from the array, but later values are still set.

E.g.

array = [123, 456, None, 789]

If you're looking up 789 it wouldn't be found in this case.

I had this before, but changed it to be safer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we can be sure that new items are always added to the array (and removed) inside parent calls, we can be sure that there will never be None caps in the array.

@davidhewitt if you're confident of that, we can go ahead with this change. Worst case we just fallback on the depth guard.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed another commit which should work more like a stack, and we'll panic if the code is wrong.

return Some(index);
}
}
if let Some(index) = first_slot {
array[index] = Some(v);
first_slot
} else {
let mut set: AHashSet<T> = AHashSet::with_capacity(ARRAY_SIZE + 1);
for existing in array.iter_mut() {
set.insert(existing.take().unwrap());
}
set.insert(v);
*self = Self::Set(set);
// id doesn't matter here as we'll be removing from a set
Some(0)

// No array slots exist; convert to set
let mut set: AHashSet<T> = AHashSet::with_capacity(ARRAY_SIZE + 1);
for existing in array.iter_mut() {
set.insert(existing.take().unwrap());
}
set.insert(v);
*self = Self::Set(set);
// id doesn't matter here as we'll be removing from a set
Some(0)
}
// https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.insert
// "If the set did not have this value present, `true` is returned."
Expand All @@ -135,6 +132,7 @@ impl<T: Eq + Hash + Clone> SmallContainer<T> {
pub fn remove(&mut self, v: &T, index: usize) {
match self {
Self::Array(array) => {
debug_assert!(array[index].as_ref() == Some(v), "remove did not match insert");
array[index] = None;
}
Self::Set(set) => {
Expand Down