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. |
| /// 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.
this if statement is redundant?
There was a problem hiding this comment.
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
| index.swap_remove(&old_val); | ||
| index.insert(result); | ||
| } | ||
| self.val_index |
There was a problem hiding this comment.
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.
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.
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.
Can we just change the interface of iter to return an ExtractSizeIterator?
| // 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.
nit: it does not need to be an IndexSet, and a HashSet should be good enough?
| val_index: LazyMapOfIndexSet, | ||
| } | ||
| #[derive(Clone)] | ||
| struct LazyMapOfIndexSet { |
There was a problem hiding this comment.
nit: consider renaming to just LazyValIndex or LazyContainerIndex?
|
|
||
| const LAZY_BOUND: usize = 30; | ||
| use dashmap::mapref::one::{Ref, RefMut}; | ||
| #[allow(dead_code)] |
There was a problem hiding this comment.
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.
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.
This will be very slow when many threads are contending to insert to the index
There was a problem hiding this comment.
Eli suggested crossbeam_queue for concurrent access.
|
From codspeed on discord fyi:
|
|
@yihozhang @MilkBlock Whats the status on this PR? Should it be closed or continued? |

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