-
Notifications
You must be signed in to change notification settings - Fork 86
struct LazyMapOfIndexSet: #708
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
base: main
Are you sure you want to change the base?
Conversation
opt DashMap<Value,IndexSet<Value>> by allowing lazily insert when read
CodSpeed Performance ReportMerging #708 will improve performances by ×2.9Comparing Summary
Benchmarks breakdown
Footnotes
|
|
Thanks for this change! Looks like it does improve performance significantly. @ezrosent said he can take a look, he is more familiar with this code. |
|
I don't quite understand what happended to [test]map. I simply refuse lazy insertion when the size of keys are too small. |
|
No idea why string_quotes get slower. It's not even relevent to containers. |
|
I am going to go back to hiding all the fast running benchmarks. They generally have had too much uncertainty to be that helpful to us in the past. |
core-relations/src/containers/mod.rs
Outdated
| /// Flushes all pending lazy insertions to the underlying map. | ||
| fn flush_pending_operations_for_key(&self, key: &Value) { | ||
| let mut pending_ops = self.pending_operations.lock().unwrap(); | ||
| if !pending_ops.is_empty() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if statement is redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, pending_operations never shrinks, even when the actual val_index is small.
We can maybe do a pending_ops.retain(|(keys, op)| !keys.is_empty()) when we find there are too many empty sets during the enumeration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plausible, added
| index.swap_remove(&old_val); | ||
| index.insert(result); | ||
| } | ||
| self.val_index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
swap_remove is always used together with an insert, so maybe we can have an update_for_all_keys that takes both the old value and the new value. This way you don't need to materialize the container twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As another optimization, currently we always materialize the container (container.iter().collect()), but when the container only has a fewer entries than (LAZY_BOUND), the materialized container is immediately discarded. Can we make insert/remove_for_all_keys take an iterator instead of an IndexSet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 ways to implement this.
pub trait ContainerValue: Hash + Eq + Clone + Send + Sync + 'static {
/// Rebuild an additional container in place according the the given [`Rebuilder`].
///
/// If this method returns `false` then the container must not have been modified (i.e. it must
/// hash to the same value, and compare equal to a copy of itself before the call).
fn rebuild_contents(&mut self, rebuilder: &dyn Rebuilder) -> bool;
/// Iterate over the contents of the container.
///
/// Note that containers can be more structured than just a sequence of values. This iterator
/// is used to populate an index that in turn is used to speed up rebuilds. If a value in the
/// container is eligible for a rebuild and it is not mentioned by this iterator, the outer
/// [`Containers`] registry may skip rebuilding this container.
fn iter(&self) -> impl Iterator<Item = Value> + '_;
}- trait revision: add
fn len()to this trait - make iterator sized_iterator
Maybe we could consider it in next PR this may require many changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just change the interface of iter to return an ExtractSizeIterator?
core-relations/src/containers/mod.rs
Outdated
| // keys and value to insert | ||
| // if user want to insert same value for all keys in IndexSet<Value>, LazyMap will put them | ||
| // in pending_insert and do the insertion for single key and remove this key in pending_insert when user want to read LazyMap | ||
| pending_operations: Arc<Mutex<Vec<(IndexSet<Value>, InsertOrRemove)>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it does not need to be an IndexSet, and a HashSet should be good enough?
core-relations/src/containers/mod.rs
Outdated
| val_index: LazyMapOfIndexSet, | ||
| } | ||
| #[derive(Clone)] | ||
| struct LazyMapOfIndexSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider renaming to just LazyValIndex or LazyContainerIndex?
core-relations/src/containers/mod.rs
Outdated
|
|
||
| const LAZY_BOUND: usize = 30; | ||
| use dashmap::mapref::one::{Ref, RefMut}; | ||
| #[allow(dead_code)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be able to remove the dead code? Unused functions can be re-implemented later easily if we want to.
|
Also, are you sure this PR makes #665 faster? If you look at the flamegraph on codspeed, most of the time is spent in dropping the E-graph... But in CLI mode, the E-graph is |
emmm seems different on my machine. main branch : d3c80a4 (HEAD -> main, myfork/main) container_to_value in top level EGraph and comment revision
Also, on this graph you can see the performance is improved in run_schedule but not egraph struct memory drop.
|
|
The graph you saw might be a function name display bug on codspeed. You can see the drop_in_place function takes 80% of time, which is hard to believe. I think the performance also depends on the number of cores and this is my hardware. I don't quite understand performance regression on your machine, maybe you could perf it? |
I opened #718 to track forgetting in the benchmark as well to get more similar performance |
I opened a support request for this in the codspeed discord. |
|
I can confirm similar speedups after rebasing from main. I think it's because of the #709 |
|
|
||
| /// Lazily removes a value for all keys in the given index set. | ||
| pub fn remove_for_all_keys(&self, keys: HashSet<Value>, value: Value) { | ||
| if keys.len() < LAZY_BOUND { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to flush all the updates before eagerly removing?
update: I think you also need to do flush_pending_operations_for_key for eager insertion, not just eager removal
| // keys and value to insert | ||
| // if user want to insert same value for all keys in IndexSet<Value>, LazyMap will put them | ||
| // in pending_insert and do the insertion for single key and remove this key in pending_insert when user want to read LazyMap | ||
| pending_operations: Arc<Mutex<Vec<(HashSet<Value>, InsertOrRemove)>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be very slow when many threads are contending to insert to the index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eli suggested crossbeam_queue for concurrent access.
|
From codspeed on discord fyi:
|

opt DashMap<Value,IndexSet> by allowing lazily insert when read