-
Notifications
You must be signed in to change notification settings - Fork 142
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
Add nodes field to EGraph #291
Conversation
I noticed that since |
In order to get more stable benchmarks I tried using a modified version of iai-callgrind: I also compared |
I like the idea here. @oflatt will know best about the interactions with proof stuff. Should we worry if the nodes Vec will grow too large (without proofs, in a long running example)? I guess we never compact the unionfind either, so at worst linear overhead I think. |
Currently, this decreases memory usage even when not using explanations (at least in the benchmarks I ran) since the memory used by |
This all kinds of begs the question: why not only store the e-nodes a single time? E-classes could refer to their nodes by id, and the memo could probably even get replaced with an |
Replacing |
Another idea would be to combine |
I was playing a bit with the idea of combining A somewhat minimal example of this is #[test]
fn dup_node() {
use SymbolLang as S;
crate::init_logger();
let mut egraph = EGraph::<S, ()>::default().with_explanations_enabled();
let y = egraph.add_uncanonical(S::leaf("y"));
let y2 = egraph.add_uncanonical(S::leaf("y2"));
egraph.add_uncanonical(S::new("f", vec![y]));
egraph.add_uncanonical(S::new("g", vec![y2]));
egraph.union(y, y2);
egraph.add_uncanonical(S::new("f", vec![y]));
egraph.add_uncanonical(S::new("g", vec![y2])); // adds the duplicate
for i in 0..egraph.total_number_of_nodes() {
let id = Id::from(i);
println!("{id}: {:?} (root: {})", egraph.id_to_node(id), egraph.find(id))
}
} which prints:
This works before and after this PR (and after this PR it also works when explanations aren't enabled) Basically what happens is that when calling This isn't a bug since calling rebuild causes the duplicate nodes to get This does give the idea of combining |
eb5b548
to
4d4c52d
Compare
@mwillsey, I noticed you recently merged #306, so I updated PR to handle |
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.
Really great work! I'm in favor of this change. It cleans up the implementation nicely.
Did you ever get performance numbers? It's probably as performant as before.
Sorry I didn't review this sooner, and great job on this high-quality PR.
for i in 0..self.explainfind.len() { | ||
let node = &self.explainfind[i]; | ||
egraph.add(node.node.clone()); | ||
pub(crate) fn with_nodes<'a>(&'a mut self, nodes: &'a [L]) -> ExplainNodes<'a, L> { |
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.
with_nodes
is a little awkward.
Another way to implement would be to keep the nodes in the Explain
struct from the start. I think that might be cleaner?
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 nodes need to be available when explanations are disabled so I couldn't only have them in the Explain
struct like they were before, I didn't want to duplicate all of the nodes by having them in both places since that would require extra unnecessarily memory, and since rust doesn't support self-referential structs, I thought this was the easiest way to not change the explain implementation to much.
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.
Hmm, perhaps the Explain struct should be around whether explanations are disabled or enabled? Or I suppose the other fields of Explain could be wrapped in an Option<OtherExplanationFields>
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.
Do you mean have
struct Explain {
nodes: Vec<L>,
rest: Option<OtherExplanationFields>,
}
I don't think this would simplify things very much, and the nodes are use outside of explanations as well so I don't see why they should be part of the explain struct.
Some of the explain methods also take in the unionfind and classes, so I could also thread the nodes as an extra parameter if you prefer.
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.
Yeah, I see what you mean.
I don't have strong feelings, the solution you have is not too bad
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.
If you're fine with it, I think I'll just leave it as it is. Is there anything you would like me to change before I merge 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.
Nope, everything looks good to me
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.
Great, Thanks
@@ -1324,14 +1400,15 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> { | |||
} | |||
} | |||
|
|||
while let Some((node, class_id)) = self.analysis_pending.pop() { | |||
while let Some(class_id) = self.analysis_pending.pop() { | |||
let node = self.nodes[usize::from(class_id)].clone(); | |||
let class_id = self.find_mut(class_id); | |||
let node_data = N::make(self, &node); |
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.
Does this mean that make
can be called with un-canonical e-class ids? If so we should at least say so in the make
docs.
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.
Does this mean that make can be called with un-canonical e-class ids?
Yes, but I think this was also true before this PR, I don't think we ever canonized the nodes in analysis_pending
, or parents
.
If so we should at least say so in the make docs.
If you want I can still add that
Yes, please. Then I'm good to merge!
…On Tue, Apr 2, 2024 at 5:18 PM David Ewert ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/egraph.rs
<#291 (comment)>:
> @@ -1324,14 +1400,15 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
}
}
- while let Some((node, class_id)) = self.analysis_pending.pop() {
+ while let Some(class_id) = self.analysis_pending.pop() {
+ let node = self.nodes[usize::from(class_id)].clone();
let class_id = self.find_mut(class_id);
let node_data = N::make(self, &node);
Does this mean that make can be called with un-canonical e-class ids?
Yes, but I think this was also true before this PR, I don't think we ever
canonized the nodes in analysis_pending, or parents.
If so we should at least say so in the make docs.
If you want I can still add that
—
Reply to this email directly, view it on GitHub
<#291 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AANTPTF4I56Y23MTFTLYGCTY3NDEPAVCNFSM6AAAAABBL53E5WVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMYTSNZVGI3TCMRTGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I decided to try implementing the changes I described in #290 for adding a
nodes
field toEGraph
. The main downside to my changes is thatEClass::parents
only returns non-canonicalId
s and not the nodes themselves, although the nodes are available viaEGraph::id_to_node
. The main user-facing advantage is thatEGraph::id_to_X
methods as well asEGraph::copy_without_unions
do not require explanations to be enabled. There are also memory usage/performance advantages since EClass.parents, EGraph.pending, and EGraph.analysis_pending, no longer need to store nodes. The nodes passed toAnalysis::make
are slightly different than before, but they are the same after canonicalization.I also eliminated the
node
field ofExplainNode
to reduce memory usage when explanations are enabled, but this did add some complexity. If this complexity isn't considered to be worth it, I could also revert the changes toexplain.rs
while preserving the rest of the changes.