-
Notifications
You must be signed in to change notification settings - Fork 2
refactor!(core): change underlying implementation of CMap2 to add sync mechanisms
#201
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
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #201 +/- ##
==========================================
- Coverage 82.12% 74.45% -7.68%
==========================================
Files 41 41
Lines 5650 6173 +523
==========================================
- Hits 4640 4596 -44
- Misses 1010 1577 +567 ☔ View full report in Codecov by Sentry. |
|
Internal beta storageThis is the storage structure of beta functions: values were stored using atomic load/write is handled internally, with no change to the public API (aside from mutability requirement). all operations on these are using a relaxed ordering, but this is subject to change. the external vector (red) is not wrapped in a synchronization layer as we consider that expending, reallocating, etc. should be done before heavily-parallel computation kernels. if, at any point, we need to do this, the mechanism used should probably be a |
unused darts trackingreminder: dart removal means (a) set its beta values to if we take the last step of the clipping routine of the if we were to parallelize this mass-deletion step, all threads would be contending over access to a single the vector itself is not wrapped in a sync layer for the same reason mentionned in the beta function storage:
|
vertex / attribute storagethe issue is illustrated here: these storages must satisfy stronger synchronization requirements. because there is an intermediate state where not all of the involved data has been attributed its final values, we need to ensure nothing can happen in that time frame for operation correctness. a first idea was to wrap each entry in a second idea was to use atomic orderings to avoid inconsistencies. it was dropped because it actually does not work.
the adopted solution is to use the transactional memory model (see comment below) |
CMap2 to add sync mechanismsCMap2 to add sync mechanisms
|
following a discussion with @cedricchevalier19, the attribute storage synchronization mechanism has been switch to use a software transactional memory (STM) implementation. the model is implemented by the // create the scope of the transaction
atomically(|trans| {
let new_v = match (
self.data[lhs_inp as usize].read(trans)?, // all ops on variable take a reference to the transaction as arg
self.data[rhs_inp as usize].read(trans)?, // all `?` call are used to catch invalid transaction
) {
(Some(v1), Some(v2)) => Some(AttributeUpdate::merge(v1, v2)),
(Some(v), None) | (None, Some(v)) => Some(AttributeUpdate::merge_incomplete(v)),
(None, None) => AttributeUpdate::merge_from_none(),
};
if new_v.is_none() {
eprintln!("W: cannot merge two null attribute value");
eprintln!(" setting new target value to `None`");
}
self.data[rhs_inp as usize].write(trans, None)?;
self.data[lhs_inp as usize].write(trans, None)?;
self.data[out as usize].write(trans, new_v)?;
Ok(())
}); |
cedricchevalier19
left a comment
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.
The mix between Atomic and Transactions is hard to follow. Can you explain what has led to these choices?
| eprintln!(" setting both new values to `None`"); | ||
| self.data[lhs_out as usize].write(trans, None)?; | ||
| self.data[rhs_out as usize].write(trans, None)?; | ||
| //self.data[inp as usize].store(None, Ordering::Release); |
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.
Why?
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 believe that if the original attribute undefined, this should be cascaded to both new attributes.
Aside from that, I think that both output values should already be undefined (making these two lines useless) because of the design of my indexing logic. I'm not 100% sure and I don't want to rely on this without proving it first (which can probably be done).
| generate_manager!(manager); | ||
| generate_compact!(storage); | ||
| assert_eq!(storage.remove(3), Some(Temperature::from(279.0))); | ||
| // what was this ? |
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.
Good question!
|
|
||
| use std::collections::BTreeSet; | ||
|
|
||
| use std::sync::atomic::{AtomicBool, Ordering}; |
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.
Why do not use transactions here too?
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.
Ok, there are two things:
-
for unused dart storage,
AtomicBoolis enough because there's no situation / operation where the validity of a dart is a critical information. This could change if we implement algorithms that delete part of the mesh sporadically, but even then it doesn't matter: What is important is that all beta function of the deleted dart are set to 0, not that it is marked. marks matter when fetching all cells after (or before) an algorithm is executed. -
for beta values: these should probably be transactional too. the biggest consequence would be that some method (e.g. attribute collection methods) need to take a transaction as argument as the
atomicallysection provided byt he stm crate should not be nested
|
I rewrote a few examples on a white board and found cases where atomics on beta values were not satisfying. given this, I changed the storage type to these changes significantly enlarge the PR, but some cleanup is possible. as of now, I duplicated most original methods to add variants taking a the most notable changes (that adds a lot of code) is the computation of new IDs in sew/unsew methods. the issue is that the ID must be computed with the edited beta values (i.e. the ones of the transaction), but adding a reference to a transaction in an |
|
Ok, I will take my time to understand the changes. |
|
following a conversation with @cedricchevalier19, this is the course of action we'll follow:
|


Clonetrait impl when featureutilswas enabled (unused after some benchmark deletion)AttrCompactVecfor nowu32by its atomic equivalentAtomicU32BTreeSetbyVec<AtomicBool>AttrSparseVecinternal storage typeOption<A>byAtomic<Option<A>>using theatomiccrateCMap2, storage traits methods, storage manager, to be non-mutableSend&Syncimplementations for:AttrSparseVec,AttrStorageManagerCMap2VertexCollection,EdgeCollection,FaceCollectionVertex2,Vector2