Skip to content

🚧 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

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
a9d5ffe
✨ Implement mark-and-sweep garbage collection and update reference ma…
q-inho Jun 2, 2025
8a3646e
✨ Refactor garbage collection root management to use unordered_set fo…
q-inho Jun 2, 2025
46d7a71
Merge branch 'main' into mark_and_sweep_garbage_DD_package
q-inho Jun 2, 2025
e03554c
Update include/mqt-core/dd/Node.hpp
q-inho Jun 2, 2025
73ef0ff
✨ Enhance mark-and-sweep garbage collection with explicit reference c…
q-inho Jun 4, 2025
2f216ef
Merge branch 'main' into mark_and_sweep_garbage_DD_package
q-inho Jun 4, 2025
ad64bf9
Fix RealNumber reference count assertion
q-inho Jun 4, 2025
46e7aa9
Update include/mqt-core/dd/Package.hpp
q-inho Jun 4, 2025
c88a319
Refactor RealNumber, ComplexNumber and UniqueTable for Improved Memor…
q-inho Jun 13, 2025
143c1db
constexpr pointer operations in `RealNumber`
q-inho Jun 13, 2025
6671038
RealNumber with mark and unmark functions
q-inho Jun 13, 2025
f5877d0
Update tests to use makeBasisState instead of makeZeroState for bette…
q-inho Jun 13, 2025
bed77c0
Revert changes
q-inho Jun 13, 2025
4a1a121
Merge remote-tracking branch 'upstream/main' into mark_and_sweep_garb…
q-inho Jun 13, 2025
7e768b6
Change the call to `makeZeroState` to the `dd` namespace
q-inho Jun 13, 2025
ce4afda
add `bit_cast` support for clearMark function in RealNumber for Build…
q-inho Jun 14, 2025
c51a5de
Merge branch 'main' into mark_and_sweep_garbage_DD_package
q-inho Jun 14, 2025
d75f141
Replace constexp to inline due to build failure on Window
q-inho Jun 15, 2025
d6dec27
Remove constexpr from exactlyZero and exactlyOne methods in Complex s…
q-inho Jun 15, 2025
8344696
Remove constexpr from isStaticComplex method in ComplexNumbers class
q-inho Jun 15, 2025
26ba8d5
🚧 work-in-progress cleanup
burgholzer Jun 15, 2025
dbde227
Merge branch 'mark_and_sweep_garbage_DD_package' into enh/mark-and-sweep
MatthiasReumann Jun 20, 2025
51d40d2
Refactor existing solution
MatthiasReumann Jun 23, 2025
938a1f0
Use next pointer for pointer tagging
MatthiasReumann Jun 23, 2025
a6a1af6
Fix segfault when pointer tagging
MatthiasReumann Jun 24, 2025
7a4dc44
Remove unnecessary <iostream> include
MatthiasReumann Jun 25, 2025
7c54040
Properly track the states in dd functionality tests
MatthiasReumann Jun 25, 2025
ca7c5cf
make dd's const
MatthiasReumann Jun 25, 2025
c5ecda7
Add missing track statements
MatthiasReumann Jun 25, 2025
7265260
Reorder untrack and track statements
MatthiasReumann Jun 26, 2025
3ede3c3
Remove static 0.5 from cUniqueTable
MatthiasReumann Jun 26, 2025
95b6f13
Restructure grover tests
MatthiasReumann Jun 26, 2025
c043a29
Remove std::dec
MatthiasReumann Jun 26, 2025
797c7fd
Use enable_if
MatthiasReumann Jun 26, 2025
673aa98
Uncomment EXPECT_EQ statements
MatthiasReumann Jun 26, 2025
4dcfda7
Update python bindings
MatthiasReumann Jun 26, 2025
7b57bf0
Fix python test
MatthiasReumann Jun 26, 2025
a63dcb4
Add missing track statements
MatthiasReumann Jun 26, 2025
39d15a1
Resolve linting issues
MatthiasReumann Jun 26, 2025
f8963c4
Add immortal numbers
MatthiasReumann Jun 27, 2025
f0e34f1
Rename immortal flag
MatthiasReumann Jun 27, 2025
6f0f429
Numerical issues resolved for Approximation Tests; revert back to bud…
MatthiasReumann Jun 27, 2025
682d51d
Remove intermediate garbage collection
MatthiasReumann Jun 27, 2025
6e04ee3
Wrap RealNumber flags in anonymous namespace
MatthiasReumann Jun 27, 2025
d858184
Make mark() and unmark() of Edge const
MatthiasReumann Jun 27, 2025
bbe4b64
Add function descriptions
MatthiasReumann Jun 27, 2025
2b258cb
Fix linting issues
MatthiasReumann Jun 28, 2025
33aa0b8
Remove redundant parentheses
MatthiasReumann Jun 28, 2025
a30d325
Remove unused import
MatthiasReumann Jun 28, 2025
24772d1
Revert back to "manage ref counting"
MatthiasReumann Jun 28, 2025
f57ad1d
Reorder functions for RealNumber
MatthiasReumann Jun 28, 2025
50ad21c
Reorder track and untrack to align with old implementation
MatthiasReumann Jun 28, 2025
7d7a3e8
Fix numerical issues in grover tests
MatthiasReumann Jun 28, 2025
32de359
Remove TODO: comment
MatthiasReumann Jun 28, 2025
3652d02
Add missing track statements
MatthiasReumann Jun 28, 2025
38ef1c6
Remove isNegativePointer check
MatthiasReumann Jun 30, 2025
74228e2
Add static check and remove additional track statements
MatthiasReumann Jun 30, 2025
acef608
Revert test_grover recursive functionality
MatthiasReumann Jun 30, 2025
7737688
Correctly use applyOperation in eval_dd
MatthiasReumann Jun 30, 2025
b7d00e9
Use applyOperation in test_grover
MatthiasReumann Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions bindings/dd/register_dd_package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,15 @@ void registerDDPackage(const py::module& mod) {
"State vector must have a length of a power of two.");
}
if (length == 1) {
return dd::vEdge::terminal(p.cn.lookup(data(0)));
const auto state = dd::vEdge::terminal(p.cn.lookup(data(0)));
p.track(state);
return state;
}

const auto level = static_cast<dd::Qubit>(std::log2(length) - 1);
const auto state = makeDDFromVector(p, data, 0, length, level);
const dd::vEdge e{state.p, p.cn.lookup(state.w)};
p.incRef(e);
p.track(e);
return e;
},
"state"_a,
Expand Down Expand Up @@ -405,11 +407,12 @@ void registerDDPackage(const py::module& mod) {
// keep the DD package alive while the returned matrix DD is alive.
py::keep_alive<0, 1>());

// Reference counting and garbage collection
dd.def("inc_ref_vec", &dd::Package::incRef<dd::vNode>, "vec"_a);
dd.def("inc_ref_mat", &dd::Package::incRef<dd::mNode>, "mat"_a);
dd.def("dec_ref_vec", &dd::Package::decRef<dd::vNode>, "vec"_a);
dd.def("dec_ref_mat", &dd::Package::decRef<dd::mNode>, "mat"_a);
// `track_*` and `untrack_*` manage the root sets. Garbage collection
// performs a mark-and-sweep pass over all nodes.
dd.def("track_vec", &dd::Package::track<dd::vNode>, "vec"_a);
dd.def("track_mat", &dd::Package::track<dd::mNode>, "mat"_a);
dd.def("untrack_vec", &dd::Package::untrack<dd::vNode>, "vec"_a);
dd.def("untrack_mat", &dd::Package::untrack<dd::mNode>, "mat"_a);
dd.def("garbage_collect", &dd::Package::garbageCollect, "force"_a = false);

// Operations on DDs
Expand Down
14 changes: 7 additions & 7 deletions eval/eval_dd_package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ benchmarkSimulateGrover(const qc::Qubit nq,
const std::bitset<128U> iterBits(iterations);
const auto msb = static_cast<std::size_t>(std::floor(std::log2(iterations)));
auto f = iter;
dd.incRef(f);
dd.track(f);
for (std::size_t j = 0U; j <= msb; ++j) {
if (iterBits[j]) {
e = dd.applyOperation(f, e);
Expand All @@ -134,7 +134,7 @@ benchmarkSimulateGrover(const qc::Qubit nq,
f = dd.applyOperation(f, f);
}
}
dd.decRef(f);
dd.untrack(f);
exp->sim = e;
const auto end = std::chrono::high_resolution_clock::now();
exp->runtime =
Expand All @@ -161,22 +161,22 @@ benchmarkFunctionalityConstructionGrover(
const std::bitset<128U> iterBits(iterations);
const auto msb = static_cast<std::size_t>(std::floor(std::log2(iterations)));
auto f = iter;
dd.incRef(f);
dd.track(f);
bool zero = !iterBits[0U];
for (std::size_t j = 1U; j <= msb; ++j) {
f = dd.applyOperation(f, f);
if (iterBits[j]) {
if (zero) {
dd.incRef(f);
dd.decRef(e);
dd.track(f);
dd.untrack(e);
e = f;
zero = false;
} else {
e = dd.applyOperation(e, f);
e = dd.applyOperation(f, e);
}
}
}
dd.decRef(f);
dd.untrack(f);

// apply state preparation setup
qc::QuantumComputation statePrep(nq + 1);
Expand Down
6 changes: 6 additions & 0 deletions include/mqt-core/dd/Complex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ struct Complex {
*/
[[nodiscard]] bool approximatelyZero() const noexcept;

/// @brief Mark the complex number as used.
void mark() const noexcept;

/// @brief Unmark the complex number.
void unmark() const noexcept;

/**
* @brief Convert the complex number to a string.
* @param formatted Whether to apply special formatting to the numbers.
Expand Down
18 changes: 0 additions & 18 deletions include/mqt-core/dd/ComplexNumbers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,24 +128,6 @@ class ComplexNumbers {
return e;
}

/**
* @brief Increment the reference count of a complex number.
* @details This is a pass-through function that increments the reference
* count of the real and imaginary parts of the given complex number.
* @param c The complex number
* @see RealNumberUniqueTable::incRef(RealNumber*)
*/
void incRef(const Complex& c) const noexcept;

/**
* @brief Decrement the reference count of a complex number.
* @details This is a pass-through function that decrements the reference
* count of the real and imaginary parts of the given complex number.
* @param c The complex number
* @see RealNumberUniqueTable::decRef(RealNumber*)
*/
void decRef(const Complex& c) const noexcept;

/**
* @brief Check whether a complex number is one of the static ones.
* @param c The complex number.
Expand Down
7 changes: 0 additions & 7 deletions include/mqt-core/dd/DDDefinitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ namespace dd {
*/
using Qubit = std::uint16_t;

/**
* @brief Integer type used for reference counting
* @details Allows a maximum reference count of roughly 4 billion.
*/
using RefCount = std::uint32_t;
static_assert(std::is_unsigned_v<RefCount>, "RefCount should be unsigned.");

/**
* @brief Floating point type to use for computations
* @note Adjusting the precision might lead to unexpected results.
Expand Down
6 changes: 6 additions & 0 deletions include/mqt-core/dd/Edge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ template <class Node> struct Edge {
*/
[[nodiscard]] std::size_t size() const;

/// @brief Mark the edge as used.
void mark() const noexcept;

/// @brief Unmark the edge.
void unmark() const noexcept;

private:
/**
* @brief Recursively traverse the DD and count the number of nodes
Expand Down
3 changes: 1 addition & 2 deletions include/mqt-core/dd/Export.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ static std::ostream& memoryNode(const Edge<Node>& e, std::ostream& os) {
os << nodelabel << "[label=<";
os << R"(<font point-size="10"><table border="1" cellspacing="0" cellpadding="2" style="rounded">)";
os << R"(<tr><td colspan=")" << n << R"(" border="1" sides="B">)" << std::hex
<< reinterpret_cast<std::uintptr_t>(e.p) << std::dec
<< " ref: " << e.p->ref << "</td></tr>";
<< reinterpret_cast<std::uintptr_t>(e.p) << std::dec << "</td></tr>";
os << "<tr>";
for (std::size_t i = 0; i < n; ++i) {
os << "<td port=\"" << i << R"(" href="javascript:;" border="0" tooltip=")"
Expand Down
75 changes: 26 additions & 49 deletions include/mqt-core/dd/Node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include <array>
#include <cassert>
#include <cstdint>
#include <limits>

namespace dd {

Expand All @@ -27,11 +26,9 @@ namespace dd {
* @details This class is used to store common information for all DD nodes.
* The `flags` makes the implicit padding explicit and can be used for storing
* node properties.
* Data Layout (8)|(4|2|2) = 16B.
* Data Layout (8)|(2|2|4) = 16B.
*/
struct NodeBase : LLBase {
/// Reference count
RefCount ref = 0;
/// Variable index
Qubit v{};

Expand All @@ -40,13 +37,28 @@ struct NodeBase : LLBase {
* @details Not required for all node types, but padding is required either
* way.
*
* 0b1000 = marks a reduced dm node,
* 0b100 = marks a dm (tmp flag),
* 0b10 = mark first path edge (tmp flag),
* 0b1 = mark path is conjugated (tmp flag)
* 0b10000 = mark flag used for mark-and-sweep garbage collection,
* 0b1000 = marks a reduced dm node,
* 0b100 = marks a dm (tmp flag),
* 0b10 = mark first path edge (tmp flag),
* 0b1 = mark path is conjugated (tmp flag)
*/
std::uint16_t flags = 0;

/// Mark flag used for mark-and-sweep garbage collection
static constexpr std::uint16_t MARK_FLAG = 0b10000U;

/// @brief Whether a node is marked as used.
[[nodiscard]] bool isMarked() const noexcept {
return (flags & MARK_FLAG) != 0U;
}

/// @brief Mark the node as used.
void mark() noexcept { flags |= MARK_FLAG; }

/// @brief Unmark the node.
void unmark() noexcept { flags &= ~MARK_FLAG; }

/// Getter for the next object.
[[nodiscard]] NodeBase* next() const noexcept {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
Expand All @@ -68,9 +80,12 @@ struct NodeBase : LLBase {
static constexpr NodeBase* getTerminal() noexcept { return nullptr; }
};

static_assert(sizeof(NodeBase) == 16);
static_assert(alignof(NodeBase) == 8);

/**
* @brief A vector DD node
* @details Data Layout (8)|(4|2|2)|(24|24) = 64B
* @details Data Layout (8)|(2|2|4)|(24|24) = 64B
*/
struct vNode final : NodeBase { // NOLINT(readability-identifier-naming)
std::array<Edge<vNode>, RADIX> e{}; // edges out of this node
Expand All @@ -89,7 +104,7 @@ using VectorDD = vEdge;

/**
* @brief A matrix DD node
* @details Data Layout (8)|(4|2|2)|(24|24|24|24) = 112B
* @details Data Layout (8)|(2|2|4)|(24|24|24|24) = 112B
*/
struct mNode final : NodeBase { // NOLINT(readability-identifier-naming)
std::array<Edge<mNode>, NEDGE> e{}; // edges out of this node
Expand All @@ -108,7 +123,7 @@ using MatrixDD = mEdge;

/**
* @brief A density matrix DD node
* @details Data Layout (8)|(4|2|2)|(24|24|24|24) = 112B
* @details Data Layout (8)|(2|2|4)|(24|24|24|24) = 112B
*/
struct dNode final : NodeBase { // NOLINT(readability-identifier-naming)
std::array<Edge<dNode>, NEDGE> e{}; // edges out of this node
Expand Down Expand Up @@ -205,42 +220,4 @@ static inline dEdge densityFromMatrixEdge(const mEdge& e) {
return dEdge{reinterpret_cast<dNode*>(e.p), e.w};
}

/**
* @brief Increment the reference count of a node.
* @details This function increments the reference count of a node. If the
* reference count has saturated (i.e. reached the maximum value of RefCount)
* the reference count is not incremented.
* @param p A pointer to the node to increment the reference count of.
* @returns Whether the reference count was incremented.
* @note Typically, you do not want to call this function directly. Instead,
* use the UniqueTable::incRef(Node*) function.
*/
[[nodiscard]] static constexpr bool incRef(NodeBase* p) noexcept {
if (p == nullptr || p->ref == std::numeric_limits<RefCount>::max()) {
return false;
}
++p->ref;
return true;
}

/**
* @brief Decrement the reference count of a node.
* @details This function decrements the reference count of a node. If the
* reference count has saturated (i.e. reached the maximum value of RefCount)
* the reference count is not decremented.
* @param p A pointer to the node to decrement the reference count of.
* @returns Whether the reference count was decremented.
* @note Typically, you do not want to call this function directly. Instead,
* use the UniqueTable::decRef(Node*) function.
*/
[[nodiscard]] static constexpr bool decRef(NodeBase* p) noexcept {
if (p == nullptr || p->ref == std::numeric_limits<RefCount>::max()) {
return false;
}
assert(p->ref != 0 &&
"Reference count of Node must not be zero before decrement");
--p->ref;
return true;
}

} // namespace dd
2 changes: 1 addition & 1 deletion include/mqt-core/dd/NoiseFunctionality.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class StochasticNoiseFunctionality {
double multiQubitGateFactor,
const std::string& cNoiseEffects);

~StochasticNoiseFunctionality() { package->decRef(identityDD); }
~StochasticNoiseFunctionality() { package->untrack(identityDD); }

protected:
Package* package;
Expand Down
13 changes: 6 additions & 7 deletions include/mqt-core/dd/Operations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ MatrixDD applyUnitaryOperation(const qc::Operation& op, const MatrixDD& in,
* @details This is a convenience function that realizes the measurement @p op
* on @p in and stores the measurement results in @p measurements. The result is
* determined based on the RNG @p rng. The function correctly accounts for the
* permutation of the operation's qubits as well as automatically handles
* reference counting.
* permutation of the operation's qubits and automatically handles reference
* counting.
*
* @param op The measurement operation to apply
* @param in The input DD
Expand All @@ -166,7 +166,7 @@ VectorDD applyMeasurement(const qc::NonUnitaryOperation& op, VectorDD in,
* in. To this end, it measures the qubit and applies an X operation if the
* measurement result is one. The result is determined based on the RNG @p rng.
* The function correctly accounts for the permutation of the operation's
* qubits as well as automatically handles reference counting.
* qubits and automatically handles reference counting.
*
* @param op The reset operation to apply
* @param in The input DD
Expand All @@ -187,8 +187,7 @@ VectorDD applyReset(const qc::NonUnitaryOperation& op, VectorDD in, Package& dd,
* operation @p op on @p in. It applies the underlying operation if the actual
* value stored in the measurement results matches the expected value according
* to the comparison kind. The function correctly accounts for the permutation
* of the operation's qubits as well as automatically handles reference
* counting.
* of the operation's qubits and automatically handles reference counting.
*
* @param op The classic controlled operation to apply
* @param in The input DD
Expand Down Expand Up @@ -295,8 +294,8 @@ void changePermutation(DDType& on, qc::Permutation& from,
}
}

dd.incRef(on);
dd.decRef(saved);
dd.track(on);
dd.untrack(saved);
dd.garbageCollect();

// update permutation
Expand Down
Loading
Loading