-
Notifications
You must be signed in to change notification settings - Fork 43
🚧 Mark-And-Sweep Garbage Collection #1020
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?
🚧 Mark-And-Sweep Garbage Collection #1020
Conversation
…nagement in DD package
…r improved performance
Co-authored-by: Lukas Burgholzer <[email protected]> Signed-off-by: Inho Choi <[email protected]>
…ounting and custom hash/equality functions for edges
Co-authored-by: Lukas Burgholzer <[email protected]> Signed-off-by: Inho Choi <[email protected]>
Signed-off-by: burgholzer <[email protected]>
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.
Just briefly had some time to look over this and wanted to leave some comments just in case. Many thanks for working on this 🙏
include/mqt-core/dd/Node.hpp
Outdated
std::uint16_t flags = 0; // TODO: Would it make sense to use a larger datatype | ||
// here since Ref is gone? |
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 was thinking about that already.
Initially, I would have hoped that we get some space savings for the nodes here, but that turned out to be wishful thinking. So we might as well make the padding explicit.
However that's also kind of awkward as there is no 48bit type.
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.
What's your take on using a bit field? All I know is that they exist.
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.
Hm. Just read through the linked page.
Sounds reasonable in principle. I am a bit worried that the standard text mentions the implementation defined nature of the allocation details quite often.
We should, at least, make sure that on the platforms that we commonly test with, the packing and alignment of the resulting struct is as we would expect it.
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.
Yes. Quick googling tells us that this might not be the most portable and efficient solution.
So I guess we stick to 32 bit for now? 🤔
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. Let's stick with that for now. We don't use most of the flag bitfield anyway.
So this is highly likely to be the most portable solution for now.
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.
Thinking about BitFields made me wondering: Wouldn't it be nice to have something like:
struct Flags { // Size: 4 bytes, alignment 4 bytes
uint32_t mark : 1;
uint32_t reduced : 1;
uint32_t dm : 1;
uint32_t firstPath : 1;
uint32_t conjugated : 1;
};
Flags f;
The compiler would give us the respective masks for free. LLVM uses it in a similar fashion too, see here.
Anyhow, probably something for a follow-up PR.
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.
That looks really reasonable. and convenient.
Seems to be something for a separate PR, but I definitely like the idea! 👍🏼
@@ -101,8 +101,8 @@ MatrixDD getInverseDD(const qc::Operation& op, Package& dd, | |||
* @brief Apply a unitary operation to a given vector DD. | |||
* | |||
* @details This is a convenience function that realizes @p op times @p in and | |||
* correctly accounts for the permutation of the operation's qubits as well as | |||
* automatically handles reference counting. |
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 I would personally prefer to keep the old wording in most of the places where this was changed. Essentially, we are still doing some kind of reference counting, but only at the top level.
include/mqt-core/dd/Package.hpp
Outdated
template <class Edge> static void mark(const RootSet<Edge>& roots) { | ||
for (auto& [edge, _] : roots) { | ||
auto e = edge; | ||
e.mark(); | ||
} | ||
} | ||
|
||
/// @brief Unmark edges contained in @p roots. | ||
template <class Edge> static void unmark(const RootSet<Edge>& roots) { | ||
for (auto& [edge, _] : roots) { | ||
auto e = edge; | ||
e.unmark(); | ||
} | ||
} |
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.
Just so that I noted it down: I am still not quite sure if this trick of copying the hashmap key is actually valid.
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 was wondering that too. Seems to work - but looks awkward. I think something like a const_cast
could be useful here.
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.
Maybe one could also just separately mark edge.p
and edge.w
. Maybe that would work without the copy because only the pointers are const, but not the data they point to 🤷🏼
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.
Much easier solution: Make Edge::mark
and Edge::unmark
const functions 🫠
Codecov ReportAttention: Patch coverage is
📢 Thoughts on this report? Let us know! |
👋🏻 @burgholzer I think this is a good point to ask you for some input. The
Thanks 🙇🏻 |
Yeah. This
Yeah. That is highly likely to be numerical issues as well. Grover is particularly sensitive to these kinds of errors.
I would very much be open to have a more fundamental change to how references are being tracked as part of the package. I agree that it feels awkward at times. I am open to ideas for how to better do this and to automate it slightly better. I hope this helps. |
const auto next = dd->multiply(iterationOp, iteration); | ||
dd->track(next); | ||
dd->untrack(iteration); // This will automatically untrack the iterationOp. | ||
iteration = next; |
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.
Interestingly, adding garbageCollect()
here causes the numerical issues.
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 suppose that might be because the tables are actually full enough so that collection happens. Without garbage collection, "dead" entries might become alive again in subsequent computations. With garbage collection, these entries might be gone and the computation might result in slightly different results due to tolerances and such.
The interesting thing is that I would not expect things to actually change based on the changes in this PR. We are still using the same criterion for when to collect garbage and we should be tracking the same DDs as previously with the more fine-grained reference counting.
Description
Continues the work from #980 and resolves issue #644.
Checklist: