/root/bitcoin/src/cluster_linearize.h
Line | Count | Source |
1 | | // Copyright (c) The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #ifndef BITCOIN_CLUSTER_LINEARIZE_H |
6 | | #define BITCOIN_CLUSTER_LINEARIZE_H |
7 | | |
8 | | #include <algorithm> |
9 | | #include <cstdint> |
10 | | #include <numeric> |
11 | | #include <optional> |
12 | | #include <utility> |
13 | | #include <vector> |
14 | | |
15 | | #include <attributes.h> |
16 | | #include <memusage.h> |
17 | | #include <random.h> |
18 | | #include <span.h> |
19 | | #include <util/feefrac.h> |
20 | | #include <util/vecdeque.h> |
21 | | |
22 | | namespace cluster_linearize { |
23 | | |
24 | | /** Data type to represent transaction indices in DepGraphs and the clusters they represent. */ |
25 | | using DepGraphIndex = uint32_t; |
26 | | |
27 | | /** Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors, |
28 | | * descendants). */ |
29 | | template<typename SetType> |
30 | | class DepGraph |
31 | | { |
32 | | /** Information about a single transaction. */ |
33 | | struct Entry |
34 | | { |
35 | | /** Fee and size of transaction itself. */ |
36 | | FeeFrac feerate; |
37 | | /** All ancestors of the transaction (including itself). */ |
38 | | SetType ancestors; |
39 | | /** All descendants of the transaction (including itself). */ |
40 | | SetType descendants; |
41 | | |
42 | | /** Equality operator (primarily for testing purposes). */ |
43 | 0 | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
44 | | |
45 | | /** Construct an empty entry. */ |
46 | 0 | Entry() noexcept = default; |
47 | | /** Construct an entry with a given feerate, ancestor set, descendant set. */ |
48 | 1.03M | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Entry::Entry(FeeFrac const&, bitset_detail::IntBitSet<unsigned int> const&, bitset_detail::IntBitSet<unsigned int> const&) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::Entry::Entry(FeeFrac const&, bitset_detail::MultiIntBitSet<unsigned long, 2u> const&, bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Entry::Entry(FeeFrac const&, bitset_detail::IntBitSet<unsigned long> const&, bitset_detail::IntBitSet<unsigned long> const&) Line | Count | Source | 48 | 1.03M | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {} |
|
49 | | }; |
50 | | |
51 | | /** Data for each transaction. */ |
52 | | std::vector<Entry> entries; |
53 | | |
54 | | /** Which positions are used. */ |
55 | | SetType m_used; |
56 | | |
57 | | public: |
58 | | /** Equality operator (primarily for testing purposes). */ |
59 | | friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept |
60 | 0 | { |
61 | 0 | if (a.m_used != b.m_used) return false; |
62 | | // Only compare the used positions within the entries vector. |
63 | 0 | for (auto idx : a.m_used) { |
64 | 0 | if (a.entries[idx] != b.entries[idx]) return false; |
65 | 0 | } |
66 | 0 | return true; |
67 | 0 | } |
68 | | |
69 | | // Default constructors. |
70 | 390k | DepGraph() noexcept = default; Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::DepGraph() Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::DepGraph() cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::DepGraph() Line | Count | Source | 70 | 390k | DepGraph() noexcept = default; |
|
71 | 0 | DepGraph(const DepGraph&) noexcept = default; |
72 | 0 | DepGraph(DepGraph&&) noexcept = default; |
73 | 0 | DepGraph& operator=(const DepGraph&) noexcept = default; |
74 | 144k | DepGraph& operator=(DepGraph&&) noexcept = default; Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::operator=(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>&&) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::operator=(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>&&) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::operator=(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>&&) Line | Count | Source | 74 | 144k | DepGraph& operator=(DepGraph&&) noexcept = default; |
|
75 | | |
76 | | /** Construct a DepGraph object given another DepGraph and a mapping from old to new. |
77 | | * |
78 | | * @param depgraph The original DepGraph that is being remapped. |
79 | | * |
80 | | * @param mapping A span such that mapping[i] gives the position in the new DepGraph |
81 | | * for position i in the old depgraph. Its size must be equal to |
82 | | * depgraph.PositionRange(). The value of mapping[i] is ignored if |
83 | | * position i is a hole in depgraph (i.e., if !depgraph.Positions()[i]). |
84 | | * |
85 | | * @param pos_range The PositionRange() for the new DepGraph. It must equal the largest |
86 | | * value in mapping for any used position in depgraph plus 1, or 0 if |
87 | | * depgraph.TxCount() == 0. |
88 | | * |
89 | | * Complexity: O(N^2) where N=depgraph.TxCount(). |
90 | | */ |
91 | 0 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) |
92 | 0 | { |
93 | 0 | Assume(mapping.size() == depgraph.PositionRange()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
94 | 0 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
95 | 0 | for (DepGraphIndex i : depgraph.Positions()) { |
96 | 0 | auto new_idx = mapping[i]; |
97 | 0 | Assume(new_idx < pos_range); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
98 | | // Add transaction. |
99 | 0 | entries[new_idx].ancestors = SetType::Singleton(new_idx); |
100 | 0 | entries[new_idx].descendants = SetType::Singleton(new_idx); |
101 | 0 | m_used.Set(new_idx); |
102 | | // Fill in fee and size. |
103 | 0 | entries[new_idx].feerate = depgraph.entries[i].feerate; |
104 | 0 | } |
105 | 0 | for (DepGraphIndex i : depgraph.Positions()) { |
106 | | // Fill in dependencies by mapping direct parents. |
107 | 0 | SetType parents; |
108 | 0 | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); |
109 | 0 | AddDependencies(parents, mapping[i]); |
110 | 0 | } |
111 | | // Verify that the provided pos_range was correct (no unused positions at the end). |
112 | 0 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
113 | 0 | } |
114 | | |
115 | | /** Get the set of transactions positions in use. Complexity: O(1). */ |
116 | 60.8M | const SetType& Positions() const noexcept { return m_used; }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Positions() const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::Positions() const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Positions() const Line | Count | Source | 116 | 60.8M | const SetType& Positions() const noexcept { return m_used; } |
|
117 | | /** Get the range of positions in this DepGraph. All entries in Positions() are in [0, PositionRange() - 1]. */ |
118 | 10.9M | DepGraphIndex PositionRange() const noexcept { return entries.size(); }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::PositionRange() const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::PositionRange() const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::PositionRange() const Line | Count | Source | 118 | 10.9M | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
|
119 | | /** Get the number of transactions in the graph. Complexity: O(1). */ |
120 | 17.9M | auto TxCount() const noexcept { return m_used.Count(); }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::TxCount() const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::TxCount() const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::TxCount() const Line | Count | Source | 120 | 17.9M | auto TxCount() const noexcept { return m_used.Count(); } |
|
121 | | /** Get the feerate of a given transaction i. Complexity: O(1). */ |
122 | 80.1M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::FeeRate(unsigned int) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::FeeRate(unsigned int) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 80.1M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; } |
|
123 | | /** Get the mutable feerate of a given transaction i. Complexity: O(1). */ |
124 | 24.0k | FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::FeeRate(unsigned int) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::FeeRate(unsigned int) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::FeeRate(unsigned int) Line | Count | Source | 124 | 24.0k | FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; } |
|
125 | | /** Get the ancestors of a given transaction i. Complexity: O(1). */ |
126 | 148M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Ancestors(unsigned int) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::Ancestors(unsigned int) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 148M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
|
127 | | /** Get the descendants of a given transaction i. Complexity: O(1). */ |
128 | 105M | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Descendants(unsigned int) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::Descendants(unsigned int) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Descendants(unsigned int) const Line | Count | Source | 128 | 105M | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; } |
|
129 | | |
130 | | /** Add a new unconnected transaction to this transaction graph (in the first available |
131 | | * position), and return its DepGraphIndex. |
132 | | * |
133 | | * Complexity: O(1) (amortized, due to resizing of backing vector). |
134 | | */ |
135 | | DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept |
136 | 1.03M | { |
137 | 1.03M | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); |
138 | 1.03M | auto available = ALL_POSITIONS - m_used; |
139 | 1.03M | Assume(available.Any()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(available.Any()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(available.Any()); Line | Count | Source | 125 | 1.03M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
140 | 1.03M | DepGraphIndex new_idx = available.First(); |
141 | 1.03M | if (new_idx == entries.size()) { |
142 | 1.03M | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); |
143 | 1.03M | } else { |
144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); |
145 | 0 | } |
146 | 1.03M | m_used.Set(new_idx); |
147 | 1.03M | return new_idx; |
148 | 1.03M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::AddTransaction(FeeFrac const&) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::AddTransaction(FeeFrac const&) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 1.03M | { | 137 | 1.03M | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 1.03M | auto available = ALL_POSITIONS - m_used; | 139 | 1.03M | Assume(available.Any()); Line | Count | Source | 125 | 1.03M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 140 | 1.03M | DepGraphIndex new_idx = available.First(); | 141 | 1.03M | if (new_idx == entries.size()) { | 142 | 1.03M | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 1.03M | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 1.03M | m_used.Set(new_idx); | 147 | 1.03M | return new_idx; | 148 | 1.03M | } |
|
149 | | |
150 | | /** Remove the specified positions from this DepGraph. |
151 | | * |
152 | | * The specified positions will no longer be part of Positions(), and dependencies with them are |
153 | | * removed. Note that due to DepGraph only tracking ancestors/descendants (and not direct |
154 | | * dependencies), if a parent is removed while a grandparent remains, the grandparent will |
155 | | * remain an ancestor. |
156 | | * |
157 | | * Complexity: O(N) where N=TxCount(). |
158 | | */ |
159 | | void RemoveTransactions(const SetType& del) noexcept |
160 | 177k | { |
161 | 177k | m_used -= del; |
162 | | // Remove now-unused trailing entries. |
163 | 777k | while (!entries.empty() && !m_used[entries.size() - 1]648k ) { |
164 | 600k | entries.pop_back(); |
165 | 600k | } |
166 | | // Remove the deleted transactions from ancestors/descendants of other transactions. Note |
167 | | // that the deleted positions will retain old feerate and dependency information. This does |
168 | | // not matter as they will be overwritten by AddTransaction if they get used again. |
169 | 177k | for (auto& entry : entries) { |
170 | 137k | entry.ancestors &= m_used; |
171 | 137k | entry.descendants &= m_used; |
172 | 137k | } |
173 | 177k | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::RemoveTransactions(bitset_detail::IntBitSet<unsigned int> const&) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::RemoveTransactions(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::RemoveTransactions(bitset_detail::IntBitSet<unsigned long> const&) Line | Count | Source | 160 | 177k | { | 161 | 177k | m_used -= del; | 162 | | // Remove now-unused trailing entries. | 163 | 777k | while (!entries.empty() && !m_used[entries.size() - 1]648k ) { | 164 | 600k | entries.pop_back(); | 165 | 600k | } | 166 | | // Remove the deleted transactions from ancestors/descendants of other transactions. Note | 167 | | // that the deleted positions will retain old feerate and dependency information. This does | 168 | | // not matter as they will be overwritten by AddTransaction if they get used again. | 169 | 177k | for (auto& entry : entries) { | 170 | 137k | entry.ancestors &= m_used; | 171 | 137k | entry.descendants &= m_used; | 172 | 137k | } | 173 | 177k | } |
|
174 | | |
175 | | /** Modify this transaction graph, adding multiple parents to a specified child. |
176 | | * |
177 | | * Complexity: O(N) where N=TxCount(). |
178 | | */ |
179 | | void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept |
180 | 1.82M | { |
181 | 1.82M | Assume(m_used[child]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_used[child]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_used[child]); Line | Count | Source | 125 | 1.82M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
182 | 1.82M | Assume(parents.IsSubsetOf(m_used)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parents.IsSubsetOf(m_used)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parents.IsSubsetOf(m_used)); Line | Count | Source | 125 | 1.82M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
183 | | // Compute the ancestors of parents that are not already ancestors of child. |
184 | 1.82M | SetType par_anc; |
185 | 1.82M | for (auto par : parents - Ancestors(child)) { |
186 | 793k | par_anc |= Ancestors(par); |
187 | 793k | } |
188 | 1.82M | par_anc -= Ancestors(child); |
189 | | // Bail out if there are no such ancestors. |
190 | 1.82M | if (par_anc.None()) return1.03M ; |
191 | | // To each such ancestor, add as descendants the descendants of the child. |
192 | 793k | const auto& chl_des = entries[child].descendants; |
193 | 2.19M | for (auto anc_of_par : par_anc) { |
194 | 2.19M | entries[anc_of_par].descendants |= chl_des; |
195 | 2.19M | } |
196 | | // To each descendant of the child, add those ancestors. |
197 | 793k | for (auto dec_of_chl : Descendants(child)) { |
198 | 793k | entries[dec_of_chl].ancestors |= par_anc; |
199 | 793k | } |
200 | 793k | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::AddDependencies(bitset_detail::IntBitSet<unsigned int> const&, unsigned int) Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::AddDependencies(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&, unsigned int) cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::AddDependencies(bitset_detail::IntBitSet<unsigned long> const&, unsigned int) Line | Count | Source | 180 | 1.82M | { | 181 | 1.82M | Assume(m_used[child]); Line | Count | Source | 125 | 1.82M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 182 | 1.82M | Assume(parents.IsSubsetOf(m_used)); Line | Count | Source | 125 | 1.82M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 1.82M | SetType par_anc; | 185 | 1.82M | for (auto par : parents - Ancestors(child)) { | 186 | 793k | par_anc |= Ancestors(par); | 187 | 793k | } | 188 | 1.82M | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 1.82M | if (par_anc.None()) return1.03M ; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 793k | const auto& chl_des = entries[child].descendants; | 193 | 2.19M | for (auto anc_of_par : par_anc) { | 194 | 2.19M | entries[anc_of_par].descendants |= chl_des; | 195 | 2.19M | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 793k | for (auto dec_of_chl : Descendants(child)) { | 198 | 793k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 793k | } | 200 | 793k | } |
|
201 | | |
202 | | /** Compute the (reduced) set of parents of node i in this graph. |
203 | | * |
204 | | * This returns the minimal subset of the parents of i whose ancestors together equal all of |
205 | | * i's ancestors (unless i is part of a cycle of dependencies). Note that DepGraph does not |
206 | | * store the set of parents; this information is inferred from the ancestor sets. |
207 | | * |
208 | | * Complexity: O(N) where N=Ancestors(i).Count() (which is bounded by TxCount()). |
209 | | */ |
210 | | SetType GetReducedParents(DepGraphIndex i) const noexcept |
211 | 3.09M | { |
212 | 3.09M | SetType parents = Ancestors(i); |
213 | 3.09M | parents.Reset(i); |
214 | 5.73M | for (auto parent : parents) { |
215 | 5.73M | if (parents[parent]) { |
216 | 5.73M | parents -= Ancestors(parent); |
217 | 5.73M | parents.Set(parent); |
218 | 5.73M | } |
219 | 5.73M | } |
220 | 3.09M | return parents; |
221 | 3.09M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::GetReducedParents(unsigned int) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::GetReducedParents(unsigned int) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 3.09M | { | 212 | 3.09M | SetType parents = Ancestors(i); | 213 | 3.09M | parents.Reset(i); | 214 | 5.73M | for (auto parent : parents) { | 215 | 5.73M | if (parents[parent]) { | 216 | 5.73M | parents -= Ancestors(parent); | 217 | 5.73M | parents.Set(parent); | 218 | 5.73M | } | 219 | 5.73M | } | 220 | 3.09M | return parents; | 221 | 3.09M | } |
|
222 | | |
223 | | /** Compute the (reduced) set of children of node i in this graph. |
224 | | * |
225 | | * This returns the minimal subset of the children of i whose descendants together equal all of |
226 | | * i's descendants (unless i is part of a cycle of dependencies). Note that DepGraph does not |
227 | | * store the set of children; this information is inferred from the descendant sets. |
228 | | * |
229 | | * Complexity: O(N) where N=Descendants(i).Count() (which is bounded by TxCount()). |
230 | | */ |
231 | | SetType GetReducedChildren(DepGraphIndex i) const noexcept |
232 | 0 | { |
233 | 0 | SetType children = Descendants(i); |
234 | 0 | children.Reset(i); |
235 | 0 | for (auto child : children) { |
236 | 0 | if (children[child]) { |
237 | 0 | children -= Descendants(child); |
238 | 0 | children.Set(child); |
239 | 0 | } |
240 | 0 | } |
241 | 0 | return children; |
242 | 0 | } |
243 | | |
244 | | /** Compute the aggregate feerate of a set of nodes in this graph. |
245 | | * |
246 | | * Complexity: O(N) where N=elems.Count(). |
247 | | **/ |
248 | | FeeFrac FeeRate(const SetType& elems) const noexcept |
249 | 0 | { |
250 | 0 | FeeFrac ret; |
251 | 0 | for (auto pos : elems) ret += entries[pos].feerate; |
252 | 0 | return ret; |
253 | 0 | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::FeeRate(bitset_detail::IntBitSet<unsigned int> const&) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::FeeRate(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) const |
254 | | |
255 | | /** Get the connected component within the subset "todo" that contains tx (which must be in |
256 | | * todo). |
257 | | * |
258 | | * Two transactions are considered connected if they are both in `todo`, and one is an ancestor |
259 | | * of the other in the entire graph (so not just within `todo`), or transitively there is a |
260 | | * path of transactions connecting them. This does mean that if `todo` contains a transaction |
261 | | * and a grandparent, but misses the parent, they will still be part of the same component. |
262 | | * |
263 | | * Complexity: O(ret.Count()). |
264 | | */ |
265 | | SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept |
266 | 32.9M | { |
267 | 32.9M | Assume(todo[tx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(todo[tx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(todo[tx]); Line | Count | Source | 125 | 32.9M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
268 | 32.9M | Assume(todo.IsSubsetOf(m_used)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(todo.IsSubsetOf(m_used)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(todo.IsSubsetOf(m_used)); Line | Count | Source | 125 | 32.9M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
269 | 32.9M | auto to_add = SetType::Singleton(tx); |
270 | 32.9M | SetType ret; |
271 | 65.8M | do { |
272 | 65.8M | SetType old = ret; |
273 | 65.9M | for (auto add : to_add) { |
274 | 65.9M | ret |= Descendants(add); |
275 | 65.9M | ret |= Ancestors(add); |
276 | 65.9M | } |
277 | 65.8M | ret &= todo; |
278 | 65.8M | to_add = ret - old; |
279 | 65.8M | } while (to_add.Any()); |
280 | 32.9M | return ret; |
281 | 32.9M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::GetConnectedComponent(bitset_detail::IntBitSet<unsigned int> const&, unsigned int) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::GetConnectedComponent(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&, unsigned int) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::GetConnectedComponent(bitset_detail::IntBitSet<unsigned long> const&, unsigned int) const Line | Count | Source | 266 | 32.9M | { | 267 | 32.9M | Assume(todo[tx]); Line | Count | Source | 125 | 32.9M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 268 | 32.9M | Assume(todo.IsSubsetOf(m_used)); Line | Count | Source | 125 | 32.9M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 269 | 32.9M | auto to_add = SetType::Singleton(tx); | 270 | 32.9M | SetType ret; | 271 | 65.8M | do { | 272 | 65.8M | SetType old = ret; | 273 | 65.9M | for (auto add : to_add) { | 274 | 65.9M | ret |= Descendants(add); | 275 | 65.9M | ret |= Ancestors(add); | 276 | 65.9M | } | 277 | 65.8M | ret &= todo; | 278 | 65.8M | to_add = ret - old; | 279 | 65.8M | } while (to_add.Any()); | 280 | 32.9M | return ret; | 281 | 32.9M | } |
|
282 | | |
283 | | /** Find some connected component within the subset "todo" of this graph. |
284 | | * |
285 | | * Specifically, this finds the connected component which contains the first transaction of |
286 | | * todo (if any). |
287 | | * |
288 | | * Complexity: O(ret.Count()). |
289 | | */ |
290 | | SetType FindConnectedComponent(const SetType& todo) const noexcept |
291 | 32.9M | { |
292 | 32.9M | if (todo.None()) return todo0 ; |
293 | 32.9M | return GetConnectedComponent(todo, todo.First()); |
294 | 32.9M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::FindConnectedComponent(bitset_detail::IntBitSet<unsigned int> const&) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::FindConnectedComponent(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::FindConnectedComponent(bitset_detail::IntBitSet<unsigned long> const&) const Line | Count | Source | 291 | 32.9M | { | 292 | 32.9M | if (todo.None()) return todo0 ; | 293 | 32.9M | return GetConnectedComponent(todo, todo.First()); | 294 | 32.9M | } |
|
295 | | |
296 | | /** Determine if a subset is connected. |
297 | | * |
298 | | * Complexity: O(subset.Count()). |
299 | | */ |
300 | | bool IsConnected(const SetType& subset) const noexcept |
301 | 32.8M | { |
302 | 32.8M | return FindConnectedComponent(subset) == subset; |
303 | 32.8M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::IsConnected(bitset_detail::IntBitSet<unsigned int> const&) const Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::IsConnected(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::IsConnected(bitset_detail::IntBitSet<unsigned long> const&) const Line | Count | Source | 301 | 32.8M | { | 302 | 32.8M | return FindConnectedComponent(subset) == subset; | 303 | 32.8M | } |
|
304 | | |
305 | | /** Determine if this entire graph is connected. |
306 | | * |
307 | | * Complexity: O(TxCount()). |
308 | | */ |
309 | 0 | bool IsConnected() const noexcept { return IsConnected(m_used); } |
310 | | |
311 | | /** Append the entries of select to list in a topologically valid order. |
312 | | * |
313 | | * Complexity: O(select.Count() * log(select.Count())). |
314 | | */ |
315 | | void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept |
316 | 0 | { |
317 | 0 | DepGraphIndex old_len = list.size(); |
318 | 0 | for (auto i : select) list.push_back(i); |
319 | 0 | std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept { |
320 | 0 | const auto a_anc_count = entries[a].ancestors.Count(); |
321 | 0 | const auto b_anc_count = entries[b].ancestors.Count(); |
322 | 0 | if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count; |
323 | 0 | return a < b; |
324 | 0 | }); |
325 | 0 | } |
326 | | |
327 | | /** Check if this graph is acyclic. */ |
328 | | bool IsAcyclic() const noexcept |
329 | 8.98M | { |
330 | 32.9M | for (auto i : Positions()) { |
331 | 32.9M | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { |
332 | 0 | return false; |
333 | 0 | } |
334 | 32.9M | } |
335 | 8.98M | return true; |
336 | 8.98M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::IsAcyclic() const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::IsAcyclic() const Line | Count | Source | 329 | 8.98M | { | 330 | 32.9M | for (auto i : Positions()) { | 331 | 32.9M | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 32.9M | } | 335 | 8.98M | return true; | 336 | 8.98M | } |
|
337 | | |
338 | | unsigned CountDependencies() const noexcept |
339 | | { |
340 | | unsigned ret = 0; |
341 | | for (auto i : Positions()) { |
342 | | ret += GetReducedParents(i).Count(); |
343 | | } |
344 | | return ret; |
345 | | } |
346 | | |
347 | | /** Reduce memory usage if possible. No observable effect. */ |
348 | | void Compact() noexcept |
349 | 966k | { |
350 | 966k | entries.shrink_to_fit(); |
351 | 966k | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Compact() cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Compact() Line | Count | Source | 349 | 966k | { | 350 | 966k | entries.shrink_to_fit(); | 351 | 966k | } |
|
352 | | |
353 | | size_t DynamicMemoryUsage() const noexcept |
354 | 10.8M | { |
355 | 10.8M | return memusage::DynamicUsage(entries); |
356 | 10.8M | } Unexecuted instantiation: cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::DynamicMemoryUsage() const cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::DynamicMemoryUsage() const Line | Count | Source | 354 | 10.8M | { | 355 | 10.8M | return memusage::DynamicUsage(entries); | 356 | 10.8M | } |
|
357 | | }; |
358 | | |
359 | | /** A set of transactions together with their aggregate feerate. */ |
360 | | template<typename SetType> |
361 | | struct SetInfo |
362 | | { |
363 | | /** The transactions in the set. */ |
364 | | SetType transactions; |
365 | | /** Their combined fee and size. */ |
366 | | FeeFrac feerate; |
367 | | |
368 | | /** Construct a SetInfo for the empty set. */ |
369 | 3.06M | SetInfo() noexcept = default; Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::SetInfo() Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::SetInfo() cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::SetInfo() Line | Count | Source | 369 | 3.06M | SetInfo() noexcept = default; |
|
370 | | |
371 | | /** Construct a SetInfo for a specified set and feerate. */ |
372 | 0 | SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {} |
373 | | |
374 | | /** Construct a SetInfo for a given transaction in a depgraph. */ |
375 | | explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept : |
376 | 39.1M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, unsigned int) Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, unsigned int) cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, unsigned int) Line | Count | Source | 376 | 39.1M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {} |
|
377 | | |
378 | | /** Construct a SetInfo for a set of transactions in a depgraph. */ |
379 | | explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept : |
380 | 0 | transactions(txn), feerate(depgraph.FeeRate(txn)) {} |
381 | | |
382 | | /** Add a transaction to this SetInfo (which must not yet be in it). */ |
383 | | void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept |
384 | 0 | { |
385 | 0 | Assume(!transactions[pos]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
386 | 0 | transactions.Set(pos); |
387 | 0 | feerate += depgraph.FeeRate(pos); |
388 | 0 | } |
389 | | |
390 | | /** Add the transactions of other to this SetInfo (no overlap allowed). */ |
391 | | SetInfo& operator|=(const SetInfo& other) noexcept |
392 | 2.24M | { |
393 | 2.24M | Assume(!transactions.Overlaps(other.transactions)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!transactions.Overlaps(other.transactions)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!transactions.Overlaps(other.transactions)); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
394 | 2.24M | transactions |= other.transactions; |
395 | 2.24M | feerate += other.feerate; |
396 | 2.24M | return *this; |
397 | 2.24M | } Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::operator|=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>> const&) Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::operator|=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&) cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::operator|=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>> const&) Line | Count | Source | 392 | 2.24M | { | 393 | 2.24M | Assume(!transactions.Overlaps(other.transactions)); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 394 | 2.24M | transactions |= other.transactions; | 395 | 2.24M | feerate += other.feerate; | 396 | 2.24M | return *this; | 397 | 2.24M | } |
|
398 | | |
399 | | /** Remove the transactions of other from this SetInfo (which must be a subset). */ |
400 | | SetInfo& operator-=(const SetInfo& other) noexcept |
401 | 3.54M | { |
402 | 3.54M | Assume(other.transactions.IsSubsetOf(transactions)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(other.transactions.IsSubsetOf(transactions)); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(other.transactions.IsSubsetOf(transactions)); Line | Count | Source | 125 | 3.54M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
403 | 3.54M | transactions -= other.transactions; |
404 | 3.54M | feerate -= other.feerate; |
405 | 3.54M | return *this; |
406 | 3.54M | } Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::operator-=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>> const&) Unexecuted instantiation: cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>::operator-=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&) cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::operator-=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>> const&) Line | Count | Source | 401 | 3.54M | { | 402 | 3.54M | Assume(other.transactions.IsSubsetOf(transactions)); Line | Count | Source | 125 | 3.54M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 403 | 3.54M | transactions -= other.transactions; | 404 | 3.54M | feerate -= other.feerate; | 405 | 3.54M | return *this; | 406 | 3.54M | } |
|
407 | | |
408 | | /** Compute the difference between this and other SetInfo (which must be a subset). */ |
409 | | SetInfo operator-(const SetInfo& other) const noexcept |
410 | | { |
411 | | Assume(other.transactions.IsSubsetOf(transactions)); |
412 | | return {transactions - other.transactions, feerate - other.feerate}; |
413 | | } |
414 | | |
415 | | /** Swap two SetInfo objects. */ |
416 | | friend void swap(SetInfo& a, SetInfo& b) noexcept |
417 | | { |
418 | | swap(a.transactions, b.transactions); |
419 | | swap(a.feerate, b.feerate); |
420 | | } |
421 | | |
422 | | /** Permit equality testing. */ |
423 | 0 | friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default; |
424 | | }; |
425 | | |
426 | | /** Compute the chunks of linearization as SetInfos. */ |
427 | | template<typename SetType> |
428 | | std::vector<SetInfo<SetType>> ChunkLinearizationInfo(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept |
429 | 9.82M | { |
430 | 9.82M | std::vector<SetInfo<SetType>> ret; |
431 | 36.0M | for (DepGraphIndex i : linearization) { |
432 | | /** The new chunk to be added, initially a singleton. */ |
433 | 36.0M | SetInfo<SetType> new_chunk(depgraph, i); |
434 | | // As long as the new chunk has a higher feerate than the last chunk so far, absorb it. |
435 | 36.0M | while (!ret.empty() && new_chunk.feerate >> ret.back().feerate26.2M ) { |
436 | 0 | new_chunk |= ret.back(); |
437 | 0 | ret.pop_back(); |
438 | 0 | } |
439 | | // Actually move that new chunk into the chunking. |
440 | 36.0M | ret.emplace_back(std::move(new_chunk)); |
441 | 36.0M | } |
442 | 9.82M | return ret; |
443 | 9.82M | } Unexecuted instantiation: std::vector<cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>, std::allocator<cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>>> cluster_linearize::ChunkLinearizationInfo<bitset_detail::IntBitSet<unsigned int>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, std::span<unsigned int const, 18446744073709551615ul>) Unexecuted instantiation: std::vector<cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>, std::allocator<cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>>> cluster_linearize::ChunkLinearizationInfo<bitset_detail::MultiIntBitSet<unsigned long, 2u>>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, std::span<unsigned int const, 18446744073709551615ul>) std::vector<cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>, std::allocator<cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>>> cluster_linearize::ChunkLinearizationInfo<bitset_detail::IntBitSet<unsigned long>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 429 | 9.82M | { | 430 | 9.82M | std::vector<SetInfo<SetType>> ret; | 431 | 36.0M | for (DepGraphIndex i : linearization) { | 432 | | /** The new chunk to be added, initially a singleton. */ | 433 | 36.0M | SetInfo<SetType> new_chunk(depgraph, i); | 434 | | // As long as the new chunk has a higher feerate than the last chunk so far, absorb it. | 435 | 36.0M | while (!ret.empty() && new_chunk.feerate >> ret.back().feerate26.2M ) { | 436 | 0 | new_chunk |= ret.back(); | 437 | 0 | ret.pop_back(); | 438 | 0 | } | 439 | | // Actually move that new chunk into the chunking. | 440 | 36.0M | ret.emplace_back(std::move(new_chunk)); | 441 | 36.0M | } | 442 | 9.82M | return ret; | 443 | 9.82M | } |
|
444 | | |
445 | | /** Compute the feerates of the chunks of linearization. Identical to ChunkLinearizationInfo, but |
446 | | * only returns the chunk feerates, not the corresponding transaction sets. */ |
447 | | template<typename SetType> |
448 | | std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept |
449 | 0 | { |
450 | 0 | std::vector<FeeFrac> ret; |
451 | 0 | for (DepGraphIndex i : linearization) { |
452 | | /** The new chunk to be added, initially a singleton. */ |
453 | 0 | auto new_chunk = depgraph.FeeRate(i); |
454 | | // As long as the new chunk has a higher feerate than the last chunk so far, absorb it. |
455 | 0 | while (!ret.empty() && new_chunk >> ret.back()) { |
456 | 0 | new_chunk += ret.back(); |
457 | 0 | ret.pop_back(); |
458 | 0 | } |
459 | | // Actually move that new chunk into the chunking. |
460 | 0 | ret.push_back(std::move(new_chunk)); |
461 | 0 | } |
462 | 0 | return ret; |
463 | 0 | } Unexecuted instantiation: std::vector<FeeFrac, std::allocator<FeeFrac>> cluster_linearize::ChunkLinearization<bitset_detail::IntBitSet<unsigned int>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, std::span<unsigned int const, 18446744073709551615ul>) Unexecuted instantiation: std::vector<FeeFrac, std::allocator<FeeFrac>> cluster_linearize::ChunkLinearization<bitset_detail::MultiIntBitSet<unsigned long, 2u>>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, std::span<unsigned int const, 18446744073709551615ul>) Unexecuted instantiation: std::vector<FeeFrac, std::allocator<FeeFrac>> cluster_linearize::ChunkLinearization<bitset_detail::IntBitSet<unsigned long>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, std::span<unsigned int const, 18446744073709551615ul>) |
464 | | |
465 | | /** Concept for function objects that return std::strong_ordering when invoked with two Args. */ |
466 | | template<typename F, typename Arg> |
467 | | concept StrongComparator = |
468 | | std::regular_invocable<F, Arg, Arg> && |
469 | | std::is_same_v<std::invoke_result_t<F, Arg, Arg>, std::strong_ordering>; |
470 | | |
471 | | /** Simple default transaction ordering function for SpanningForestState::GetLinearization() and |
472 | | * Linearize(), which just sorts by DepGraphIndex. */ |
473 | | using IndexTxOrder = std::compare_three_way; |
474 | | |
475 | | /** A default cost model for SFL for SetType=BitSet<64>, based on benchmarks. |
476 | | * |
477 | | * The numbers here were obtained in February 2026 by: |
478 | | * - For a variety of machines: |
479 | | * - Running a fixed collection of ~385000 clusters found through random generation and fuzzing, |
480 | | * optimizing for difficulty of linearization. |
481 | | * - Linearize each ~3000 times, with different random seeds. Sometimes without input |
482 | | * linearization, sometimes with a bad one. |
483 | | * - Gather cycle counts for each of the operations included in this cost model, |
484 | | * broken down by their parameters. |
485 | | * - Correct the data by subtracting the runtime of obtaining the cycle count. |
486 | | * - Drop the 5% top and bottom samples from each cycle count dataset, and compute the average |
487 | | * of the remaining samples. |
488 | | * - For each operation, fit a least-squares linear function approximation through the samples. |
489 | | * - Rescale all machine expressions to make their total time match, as we only care about |
490 | | * relative cost of each operation. |
491 | | * - Take the per-operation average of operation expressions across all machines, to construct |
492 | | * expressions for an average machine. |
493 | | * - Approximate the result with integer coefficients. Each cost unit corresponds to somewhere |
494 | | * between 0.5 ns and 2.5 ns, depending on the hardware. |
495 | | */ |
496 | | class SFLDefaultCostModel |
497 | | { |
498 | | uint64_t m_cost{0}; |
499 | | |
500 | | public: |
501 | 821k | inline void InitializeBegin() noexcept {} |
502 | | inline void InitializeEnd(int num_txns, int num_deps) noexcept |
503 | 821k | { |
504 | | // Cost of initialization. |
505 | 821k | m_cost += 39 * num_txns; |
506 | | // Cost of producing linearization at the end. |
507 | 821k | m_cost += 48 * num_txns + 4 * num_deps; |
508 | 821k | } |
509 | 821k | inline void GetLinearizationBegin() noexcept {} |
510 | | inline void GetLinearizationEnd(int num_txns, int num_deps) noexcept |
511 | 821k | { |
512 | | // Note that we account for the cost of the final linearization at the beginning (see |
513 | | // InitializeEnd), because the cost budget decision needs to be made before calling |
514 | | // GetLinearization. |
515 | | // This function exists here to allow overriding it easily for benchmark purposes. |
516 | 821k | } |
517 | 784k | inline void MakeTopologicalBegin() noexcept {} |
518 | | inline void MakeTopologicalEnd(int num_chunks, int num_steps) noexcept |
519 | 784k | { |
520 | 784k | m_cost += 20 * num_chunks + 28 * num_steps; |
521 | 784k | } |
522 | 821k | inline void StartOptimizingBegin() noexcept {} |
523 | 821k | inline void StartOptimizingEnd(int num_chunks) noexcept { m_cost += 13 * num_chunks; } |
524 | 2.24M | inline void ActivateBegin() noexcept {} |
525 | 2.24M | inline void ActivateEnd(int num_deps) noexcept { m_cost += 10 * num_deps + 1; } |
526 | 2.24M | inline void DeactivateBegin() noexcept {} |
527 | 2.24M | inline void DeactivateEnd(int num_deps) noexcept { m_cost += 11 * num_deps + 8; } |
528 | 2.24M | inline void MergeChunksBegin() noexcept {} |
529 | 2.24M | inline void MergeChunksMid(int num_txns) noexcept { m_cost += 2 * num_txns; } |
530 | 2.24M | inline void MergeChunksEnd(int num_steps) noexcept { m_cost += 3 * num_steps + 5; } |
531 | 6.10M | inline void PickMergeCandidateBegin() noexcept {} |
532 | 6.10M | inline void PickMergeCandidateEnd(int num_steps) noexcept { m_cost += 8 * num_steps; } |
533 | 821k | inline void PickChunkToOptimizeBegin() noexcept {} |
534 | 821k | inline void PickChunkToOptimizeEnd(int num_steps) noexcept { m_cost += num_steps + 4; } |
535 | 821k | inline void PickDependencyToSplitBegin() noexcept {} |
536 | 821k | inline void PickDependencyToSplitEnd(int num_txns) noexcept { m_cost += 8 * num_txns + 9; } |
537 | 821k | inline void StartMinimizingBegin() noexcept {} |
538 | 821k | inline void StartMinimizingEnd(int num_chunks) noexcept { m_cost += 18 * num_chunks; } |
539 | 6.16M | inline void MinimizeStepBegin() noexcept {} |
540 | 6.16M | inline void MinimizeStepMid(int num_txns) noexcept { m_cost += 11 * num_txns + 11; } |
541 | 2.24M | inline void MinimizeStepEnd(bool split) noexcept { m_cost += 17 * split + 7; } |
542 | | |
543 | 8.63M | inline uint64_t GetCost() const noexcept { return m_cost; } |
544 | | }; |
545 | | |
546 | | /** Class to represent the internal state of the spanning-forest linearization (SFL) algorithm. |
547 | | * |
548 | | * At all times, each dependency is marked as either "active" or "inactive". The subset of active |
549 | | * dependencies is the state of the SFL algorithm. The implementation maintains several other |
550 | | * values to speed up operations, but everything is ultimately a function of what that subset of |
551 | | * active dependencies is. |
552 | | * |
553 | | * Given such a subset, define a chunk as the set of transactions that are connected through active |
554 | | * dependencies (ignoring their parent/child direction). Thus, every state implies a particular |
555 | | * partitioning of the graph into chunks (including potential singletons). In the extreme, each |
556 | | * transaction may be in its own chunk, or in the other extreme all transactions may form a single |
557 | | * chunk. A chunk's feerate is its total fee divided by its total size. |
558 | | * |
559 | | * The algorithm consists of switching dependencies between active and inactive. The final |
560 | | * linearization that is produced at the end consists of these chunks, sorted from high to low |
561 | | * feerate, each individually sorted in an arbitrary but topological (= no child before parent) |
562 | | * way. |
563 | | * |
564 | | * We define four quality properties the state can have: |
565 | | * |
566 | | * - acyclic: The state is acyclic whenever no cycle of active dependencies exists within the |
567 | | * graph, ignoring the parent/child direction. This is equivalent to saying that within |
568 | | * each chunk the set of active dependencies form a tree, and thus the overall set of |
569 | | * active dependencies in the graph form a spanning forest, giving the algorithm its |
570 | | * name. Being acyclic is also equivalent to every chunk of N transactions having |
571 | | * exactly N-1 active dependencies. |
572 | | * |
573 | | * For example in a diamond graph, D->{B,C}->A, the 4 dependencies cannot be |
574 | | * simultaneously active. If at least one is inactive, the state is acyclic. |
575 | | * |
576 | | * The algorithm maintains an acyclic state at *all* times as an invariant. This implies |
577 | | * that activating a dependency always corresponds to merging two chunks, and that |
578 | | * deactivating one always corresponds to splitting two chunks. |
579 | | * |
580 | | * - topological: We say the state is topological whenever it is acyclic and no inactive dependency |
581 | | * exists between two distinct chunks such that the child chunk has higher or equal |
582 | | * feerate than the parent chunk. |
583 | | * |
584 | | * The relevance is that whenever the state is topological, the produced output |
585 | | * linearization will be topological too (i.e., not have children before parents). |
586 | | * Note that the "or equal" part of the definition matters: if not, one can end up |
587 | | * in a situation with mutually-dependent equal-feerate chunks that cannot be |
588 | | * linearized. For example C->{A,B} and D->{A,B}, with C->A and D->B active. The AC |
589 | | * chunk depends on DB through C->B, and the BD chunk depends on AC through D->A. |
590 | | * Merging them into a single ABCD chunk fixes this. |
591 | | * |
592 | | * The algorithm attempts to keep the state topological as much as possible, so it |
593 | | * can be interrupted to produce an output whenever, but will sometimes need to |
594 | | * temporarily deviate from it when improving the state. |
595 | | * |
596 | | * - optimal: For every active dependency, define its top and bottom set as the set of transactions |
597 | | * in the chunks that would result if the dependency were deactivated; the top being the |
598 | | * one with the dependency's parent, and the bottom being the one with the child. Note |
599 | | * that due to acyclicity, every deactivation splits a chunk exactly in two. |
600 | | * |
601 | | * We say the state is optimal whenever it is topological and it has no active |
602 | | * dependency whose top feerate is strictly higher than its bottom feerate. The |
603 | | * relevance is that it can be proven that whenever the state is optimal, the produced |
604 | | * linearization will also be optimal (in the convexified feerate diagram sense). It can |
605 | | * also be proven that for every graph at least one optimal state exists. |
606 | | * |
607 | | * Note that it is possible for the SFL state to not be optimal, but the produced |
608 | | * linearization to still be optimal. This happens when the chunks of a state are |
609 | | * identical to those of an optimal state, but the exact set of active dependencies |
610 | | * within a chunk differ in such a way that the state optimality condition is not |
611 | | * satisfied. Thus, the state being optimal is more a "the eventual output is *known* |
612 | | * to be optimal". |
613 | | * |
614 | | * - minimal: We say the state is minimal when it is: |
615 | | * - acyclic |
616 | | * - topological, except that inactive dependencies between equal-feerate chunks are |
617 | | * allowed as long as they do not form a loop. |
618 | | * - like optimal, no active dependencies whose top feerate is strictly higher than |
619 | | * the bottom feerate are allowed. |
620 | | * - no chunk contains a proper non-empty subset which includes all its own in-chunk |
621 | | * dependencies of the same feerate as the chunk itself. |
622 | | * |
623 | | * A minimal state effectively corresponds to an optimal state, where every chunk has |
624 | | * been split into its minimal equal-feerate components. |
625 | | * |
626 | | * The algorithm terminates whenever a minimal state is reached. |
627 | | * |
628 | | * |
629 | | * This leads to the following high-level algorithm: |
630 | | * - Start with all dependencies inactive, and thus all transactions in their own chunk. This is |
631 | | * definitely acyclic. |
632 | | * - Activate dependencies (merging chunks) until the state is topological. |
633 | | * - Loop until optimal (no dependencies with higher-feerate top than bottom), or time runs out: |
634 | | * - Deactivate a violating dependency, potentially making the state non-topological. |
635 | | * - Activate other dependencies to make the state topological again. |
636 | | * - If there is time left and the state is optimal: |
637 | | * - Attempt to split chunks into equal-feerate parts without mutual dependencies between them. |
638 | | * When this succeeds, recurse into them. |
639 | | * - If no such chunks can be found, the state is minimal. |
640 | | * - Output the chunks from high to low feerate, each internally sorted topologically. |
641 | | * |
642 | | * When merging, we always either: |
643 | | * - Merge upwards: merge a chunk with the lowest-feerate other chunk it depends on, among those |
644 | | * with lower or equal feerate than itself. |
645 | | * - Merge downwards: merge a chunk with the highest-feerate other chunk that depends on it, among |
646 | | * those with higher or equal feerate than itself. |
647 | | * |
648 | | * Using these strategies in the improvement loop above guarantees that the output linearization |
649 | | * after a deactivate + merge step is never worse or incomparable (in the convexified feerate |
650 | | * diagram sense) than the output linearization that would be produced before the step. With that, |
651 | | * we can refine the high-level algorithm to: |
652 | | * - Start with all dependencies inactive. |
653 | | * - Perform merges as described until none are possible anymore, making the state topological. |
654 | | * - Loop until optimal or time runs out: |
655 | | * - Pick a dependency D to deactivate among those with higher feerate top than bottom. |
656 | | * - Deactivate D, causing the chunk it is in to split into top T and bottom B. |
657 | | * - Do an upwards merge of T, if possible. If so, repeat the same with the merged result. |
658 | | * - Do a downwards merge of B, if possible. If so, repeat the same with the merged result. |
659 | | * - Split chunks further to obtain a minimal state, see below. |
660 | | * - Output the chunks from high to low feerate, each internally sorted topologically. |
661 | | * |
662 | | * Instead of performing merges arbitrarily to make the initial state topological, it is possible |
663 | | * to do so guided by an existing linearization. This has the advantage that the state's would-be |
664 | | * output linearization is immediately as good as the existing linearization it was based on: |
665 | | * - Start with all dependencies inactive. |
666 | | * - For each transaction t in the existing linearization: |
667 | | * - Find the chunk C that transaction is in (which will be singleton). |
668 | | * - Do an upwards merge of C, if possible. If so, repeat the same with the merged result. |
669 | | * No downwards merges are needed in this case. |
670 | | * |
671 | | * After reaching an optimal state, it can be transformed into a minimal state by attempting to |
672 | | * split chunks further into equal-feerate parts. To do so, pick a specific transaction in each |
673 | | * chunk (the pivot), and rerun the above split-then-merge procedure again: |
674 | | * - first, while pretending the pivot transaction has an infinitesimally higher (or lower) fee |
675 | | * than it really has. If a split exists with the pivot in the top part (or bottom part), this |
676 | | * will find it. |
677 | | * - if that fails to split, repeat while pretending the pivot transaction has an infinitesimally |
678 | | * lower (or higher) fee. If a split exists with the pivot in the bottom part (or top part), this |
679 | | * will find it. |
680 | | * - if either succeeds, repeat the procedure for the newly found chunks to split them further. |
681 | | * If not, the chunk is already minimal. |
682 | | * If the chunk can be split into equal-feerate parts, then the pivot must exist in either the top |
683 | | * or bottom part of that potential split. By trying both with the same pivot, if a split exists, |
684 | | * it will be found. |
685 | | * |
686 | | * What remains to be specified are a number of heuristics: |
687 | | * |
688 | | * - How to decide which chunks to merge: |
689 | | * - The merge upwards and downward rules specify that the lowest-feerate respectively |
690 | | * highest-feerate candidate chunk is merged with, but if there are multiple equal-feerate |
691 | | * candidates, a uniformly random one among them is picked. |
692 | | * |
693 | | * - How to decide what dependency to activate (when merging chunks): |
694 | | * - After picking two chunks to be merged (see above), a uniformly random dependency between the |
695 | | * two chunks is activated. |
696 | | * |
697 | | * - How to decide which chunk to find a dependency to split in: |
698 | | * - A round-robin queue of chunks to improve is maintained. The initial ordering of this queue |
699 | | * is uniformly randomly permuted. |
700 | | * |
701 | | * - How to decide what dependency to deactivate (when splitting chunks): |
702 | | * - Inside the selected chunk (see above), among the dependencies whose top feerate is strictly |
703 | | * higher than its bottom feerate in the selected chunk, if any, a uniformly random dependency |
704 | | * is deactivated. |
705 | | * - After every split, it is possible that the top and the bottom chunk merge with each other |
706 | | * again in the merge sequence (through a top->bottom dependency, not through the deactivated |
707 | | * one, which was bottom->top). Call this a self-merge. If a self-merge does not occur after |
708 | | * a split, the resulting linearization is strictly improved (the area under the convexified |
709 | | * feerate diagram increases by at least gain/2), while self-merges do not change it. |
710 | | * |
711 | | * - How to decide the exact output linearization: |
712 | | * - When there are multiple equal-feerate chunks with no dependencies between them, pick the |
713 | | * smallest one first. If there are multiple smallest ones, pick the one that contains the |
714 | | * last transaction (according to the provided fallback order) last (note that this is not the |
715 | | * same as picking the chunk with the first transaction first). |
716 | | * - Within chunks, pick among all transactions without missing dependencies the one with the |
717 | | * highest individual feerate. If there are multiple ones with the same individual feerate, |
718 | | * pick the smallest first. If there are multiple with the same fee and size, pick the one |
719 | | * that sorts first according to the fallback order first. |
720 | | */ |
721 | | template<typename SetType, typename CostModel = SFLDefaultCostModel> |
722 | | class SpanningForestState |
723 | | { |
724 | | private: |
725 | | /** Internal RNG. */ |
726 | | InsecureRandomContext m_rng; |
727 | | |
728 | | /** Data type to represent indexing into m_tx_data. */ |
729 | | using TxIdx = DepGraphIndex; |
730 | | /** Data type to represent indexing into m_set_info. Use the smallest type possible to improve |
731 | | * cache locality. */ |
732 | | using SetIdx = std::conditional_t<(SetType::Size() <= 0xff), |
733 | | uint8_t, |
734 | | std::conditional_t<(SetType::Size() <= 0xffff), |
735 | | uint16_t, |
736 | | uint32_t>>; |
737 | | /** An invalid SetIdx. */ |
738 | | static constexpr SetIdx INVALID_SET_IDX = SetIdx(-1); |
739 | | |
740 | | /** Structure with information about a single transaction. */ |
741 | | struct TxData { |
742 | | /** The top set for every active child dependency this transaction has, indexed by child |
743 | | * TxIdx. Only defined for indexes in active_children. */ |
744 | | std::array<SetIdx, SetType::Size()> dep_top_idx; |
745 | | /** The set of parent transactions of this transaction. Immutable after construction. */ |
746 | | SetType parents; |
747 | | /** The set of child transactions of this transaction. Immutable after construction. */ |
748 | | SetType children; |
749 | | /** The set of child transactions reachable through an active dependency. */ |
750 | | SetType active_children; |
751 | | /** Which chunk this transaction belongs to. */ |
752 | | SetIdx chunk_idx; |
753 | | }; |
754 | | |
755 | | /** The set of all TxIdx's of transactions in the cluster indexing into m_tx_data. */ |
756 | | SetType m_transaction_idxs; |
757 | | /** The set of all chunk SetIdx's. This excludes the SetIdxs that refer to active |
758 | | * dependencies' tops. */ |
759 | | SetType m_chunk_idxs; |
760 | | /** The set of all SetIdx's that appear in m_suboptimal_chunks. Note that they do not need to |
761 | | * be chunks: some of these sets may have been converted to a dependency's top set since being |
762 | | * added to m_suboptimal_chunks. */ |
763 | | SetType m_suboptimal_idxs; |
764 | | /** Information about each transaction (and chunks). Keeps the "holes" from DepGraph during |
765 | | * construction. Indexed by TxIdx. */ |
766 | | std::vector<TxData> m_tx_data; |
767 | | /** Information about each set (chunk, or active dependency top set). Indexed by SetIdx. */ |
768 | | std::vector<SetInfo<SetType>> m_set_info; |
769 | | /** For each chunk, indexed by SetIdx, the set of out-of-chunk reachable transactions, in the |
770 | | * upwards (.first) and downwards (.second) direction. */ |
771 | | std::vector<std::pair<SetType, SetType>> m_reachable; |
772 | | /** A FIFO of chunk SetIdxs for chunks that may be improved still. */ |
773 | | VecDeque<SetIdx> m_suboptimal_chunks; |
774 | | /** A FIFO of chunk indexes with a pivot transaction in them, and a flag to indicate their |
775 | | * status: |
776 | | * - bit 1: currently attempting to move the pivot down, rather than up. |
777 | | * - bit 2: this is the second stage, so we have already tried moving the pivot in the other |
778 | | * direction. |
779 | | */ |
780 | | VecDeque<std::tuple<SetIdx, TxIdx, unsigned>> m_nonminimal_chunks; |
781 | | |
782 | | /** The DepGraph we are trying to linearize. */ |
783 | | const DepGraph<SetType>& m_depgraph; |
784 | | |
785 | | /** Accounting for the cost of this computation. */ |
786 | | CostModel m_cost; |
787 | | |
788 | | /** Pick a random transaction within a set (which must be non-empty). */ |
789 | | TxIdx PickRandomTx(const SetType& tx_idxs) noexcept |
790 | 3.06M | { |
791 | 3.06M | Assume(tx_idxs.Any()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(tx_idxs.Any()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(tx_idxs.Any()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
792 | 3.06M | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); |
793 | 4.58M | for (auto tx_idx : tx_idxs) { |
794 | 4.58M | if (pos == 0) return tx_idx3.06M ; |
795 | 1.51M | --pos; |
796 | 1.51M | } |
797 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
798 | 0 | return TxIdx(-1); |
799 | 3.06M | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::IntBitSet<unsigned int> const&) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::MultiIntBitSet<unsigned long, 2u> const&) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::IntBitSet<unsigned long> const&) Line | Count | Source | 790 | 3.06M | { | 791 | 3.06M | Assume(tx_idxs.Any()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 792 | 3.06M | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 4.58M | for (auto tx_idx : tx_idxs) { | 794 | 4.58M | if (pos == 0) return tx_idx3.06M ; | 795 | 1.51M | --pos; | 796 | 1.51M | } | 797 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 798 | 0 | return TxIdx(-1); | 799 | 3.06M | } |
|
800 | | |
801 | | /** Find the set of out-of-chunk transactions reachable from tx_idxs, both in upwards and |
802 | | * downwards direction. Only used by SanityCheck to verify the precomputed reachable sets in |
803 | | * m_reachable that are maintained by Activate/Deactivate. */ |
804 | | std::pair<SetType, SetType> GetReachable(const SetType& tx_idxs) const noexcept |
805 | 0 | { |
806 | 0 | SetType parents, children; |
807 | 0 | for (auto tx_idx : tx_idxs) { |
808 | 0 | const auto& tx_data = m_tx_data[tx_idx]; |
809 | 0 | parents |= tx_data.parents; |
810 | 0 | children |= tx_data.children; |
811 | 0 | } |
812 | 0 | return {parents - tx_idxs, children - tx_idxs}; |
813 | 0 | } |
814 | | |
815 | | /** Make the inactive dependency from child to parent, which must not be in the same chunk |
816 | | * already, active. Returns the merged chunk idx. */ |
817 | | SetIdx Activate(TxIdx parent_idx, TxIdx child_idx) noexcept |
818 | 2.24M | { |
819 | 2.24M | m_cost.ActivateBegin(); |
820 | | // Gather and check information about the parent and child transactions. |
821 | 2.24M | auto& parent_data = m_tx_data[parent_idx]; |
822 | 2.24M | auto& child_data = m_tx_data[child_idx]; |
823 | 2.24M | Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
824 | 2.24M | Assume(!parent_data.active_children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!parent_data.active_children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!parent_data.active_children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
825 | | // Get the set index of the chunks the parent and child are currently in. The parent chunk |
826 | | // will become the top set of the newly activated dependency, while the child chunk will be |
827 | | // grown to become the merged chunk. |
828 | 2.24M | auto parent_chunk_idx = parent_data.chunk_idx; |
829 | 2.24M | auto child_chunk_idx = child_data.chunk_idx; |
830 | 2.24M | Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
831 | 2.24M | Assume(m_chunk_idxs[parent_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[parent_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[parent_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
832 | 2.24M | Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
833 | 2.24M | auto& top_info = m_set_info[parent_chunk_idx]; |
834 | 2.24M | auto& bottom_info = m_set_info[child_chunk_idx]; |
835 | | |
836 | | // Consider the following example: |
837 | | // |
838 | | // A A There are two chunks, ABC and DEF, and the inactive E->C dependency |
839 | | // / \ / \ is activated, resulting in a single chunk ABCDEF. |
840 | | // B C B C |
841 | | // : ==> | Dependency | top set before | top set after | change |
842 | | // D E D E B->A | AC | ACDEF | +DEF |
843 | | // \ / \ / C->A | AB | AB | |
844 | | // F F F->D | D | D | |
845 | | // F->E | E | ABCE | +ABC |
846 | | // |
847 | | // The common pattern here is that any dependency which has the parent or child of the |
848 | | // dependency being activated (E->C here) in its top set, will have the opposite part added |
849 | | // to it. This is true for B->A and F->E, but not for C->A and F->D. |
850 | | // |
851 | | // Traverse the old parent chunk top_info (ABC in example), and add bottom_info (DEF) to |
852 | | // every dependency's top set which has the parent (C) in it. At the same time, change the |
853 | | // chunk_idx for each to be child_chunk_idx, which becomes the set for the merged chunk. |
854 | 5.71M | for (auto tx_idx : top_info.transactions) { |
855 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; |
856 | 5.71M | tx_data.chunk_idx = child_chunk_idx; |
857 | 5.71M | for (auto dep_child_idx : tx_data.active_children) { |
858 | 3.47M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
859 | 3.47M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info0 ; |
860 | 3.47M | } |
861 | 5.71M | } |
862 | | // Traverse the old child chunk bottom_info (DEF in example), and add top_info (ABC) to |
863 | | // every dependency's top set which has the child (E) in it. |
864 | 2.24M | for (auto tx_idx : bottom_info.transactions) { |
865 | 2.24M | auto& tx_data = m_tx_data[tx_idx]; |
866 | 2.24M | for (auto dep_child_idx : tx_data.active_children) { |
867 | 41 | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
868 | 41 | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; |
869 | 41 | } |
870 | 2.24M | } |
871 | | // Merge top_info into bottom_info, which becomes the merged chunk. |
872 | 2.24M | bottom_info |= top_info; |
873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input |
874 | | // chunks' reachable sets. |
875 | 2.24M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; |
876 | 2.24M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; |
877 | 2.24M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; |
878 | 2.24M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; |
879 | | // Make parent chunk the set for the new active dependency. |
880 | 2.24M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; |
881 | 2.24M | parent_data.active_children.Set(child_idx); |
882 | 2.24M | m_chunk_idxs.Reset(parent_chunk_idx); |
883 | | // Return the newly merged chunk. |
884 | 2.24M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); |
885 | 2.24M | return child_chunk_idx; |
886 | 2.24M | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 2.24M | { | 819 | 2.24M | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 2.24M | auto& parent_data = m_tx_data[parent_idx]; | 822 | 2.24M | auto& child_data = m_tx_data[child_idx]; | 823 | 2.24M | Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 824 | 2.24M | Assume(!parent_data.active_children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 825 | | // Get the set index of the chunks the parent and child are currently in. The parent chunk | 826 | | // will become the top set of the newly activated dependency, while the child chunk will be | 827 | | // grown to become the merged chunk. | 828 | 2.24M | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 2.24M | auto child_chunk_idx = child_data.chunk_idx; | 830 | 2.24M | Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 831 | 2.24M | Assume(m_chunk_idxs[parent_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 832 | 2.24M | Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 833 | 2.24M | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 2.24M | auto& bottom_info = m_set_info[child_chunk_idx]; | 835 | | | 836 | | // Consider the following example: | 837 | | // | 838 | | // A A There are two chunks, ABC and DEF, and the inactive E->C dependency | 839 | | // / \ / \ is activated, resulting in a single chunk ABCDEF. | 840 | | // B C B C | 841 | | // : ==> | Dependency | top set before | top set after | change | 842 | | // D E D E B->A | AC | ACDEF | +DEF | 843 | | // \ / \ / C->A | AB | AB | | 844 | | // F F F->D | D | D | | 845 | | // F->E | E | ABCE | +ABC | 846 | | // | 847 | | // The common pattern here is that any dependency which has the parent or child of the | 848 | | // dependency being activated (E->C here) in its top set, will have the opposite part added | 849 | | // to it. This is true for B->A and F->E, but not for C->A and F->D. | 850 | | // | 851 | | // Traverse the old parent chunk top_info (ABC in example), and add bottom_info (DEF) to | 852 | | // every dependency's top set which has the parent (C) in it. At the same time, change the | 853 | | // chunk_idx for each to be child_chunk_idx, which becomes the set for the merged chunk. | 854 | 5.71M | for (auto tx_idx : top_info.transactions) { | 855 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 5.71M | tx_data.chunk_idx = child_chunk_idx; | 857 | 5.71M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 3.47M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 3.47M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info0 ; | 860 | 3.47M | } | 861 | 5.71M | } | 862 | | // Traverse the old child chunk bottom_info (DEF in example), and add top_info (ABC) to | 863 | | // every dependency's top set which has the child (E) in it. | 864 | 2.24M | for (auto tx_idx : bottom_info.transactions) { | 865 | 2.24M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 2.24M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 41 | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 41 | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 41 | } | 870 | 2.24M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 2.24M | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 2.24M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 2.24M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 2.24M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 2.24M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 2.24M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 2.24M | parent_data.active_children.Set(child_idx); | 882 | 2.24M | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 2.24M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 2.24M | return child_chunk_idx; | 886 | 2.24M | } |
|
887 | | |
888 | | /** Make a specified active dependency inactive. Returns the created parent and child chunk |
889 | | * indexes. */ |
890 | | std::pair<SetIdx, SetIdx> Deactivate(TxIdx parent_idx, TxIdx child_idx) noexcept |
891 | 2.24M | { |
892 | 2.24M | m_cost.DeactivateBegin(); |
893 | | // Gather and check information about the parent transactions. |
894 | 2.24M | auto& parent_data = m_tx_data[parent_idx]; |
895 | 2.24M | Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
896 | 2.24M | Assume(parent_data.active_children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.active_children[child_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_data.active_children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
897 | | // Get the top set of the active dependency (which will become the parent chunk) and the |
898 | | // chunk set the transactions are currently in (which will become the bottom chunk). |
899 | 2.24M | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; |
900 | 2.24M | auto child_chunk_idx = parent_data.chunk_idx; |
901 | 2.24M | Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
902 | 2.24M | Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
903 | 2.24M | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
904 | 2.24M | auto& top_info = m_set_info[parent_chunk_idx]; |
905 | 2.24M | auto& bottom_info = m_set_info[child_chunk_idx]; |
906 | | |
907 | | // Remove the active dependency. |
908 | 2.24M | parent_data.active_children.Reset(child_idx); |
909 | 2.24M | m_chunk_idxs.Set(parent_chunk_idx); |
910 | 2.24M | auto ntx = bottom_info.transactions.Count(); |
911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. |
912 | 2.24M | bottom_info -= top_info; |
913 | | // See the comment above in Activate(). We perform the opposite operations here, removing |
914 | | // instead of adding. Simultaneously, aggregate the top/bottom's union of parents/children. |
915 | 2.24M | SetType top_parents, top_children; |
916 | 3.55M | for (auto tx_idx : top_info.transactions) { |
917 | 3.55M | auto& tx_data = m_tx_data[tx_idx]; |
918 | 3.55M | tx_data.chunk_idx = parent_chunk_idx; |
919 | 3.55M | top_parents |= tx_data.parents; |
920 | 3.55M | top_children |= tx_data.children; |
921 | 3.55M | for (auto dep_child_idx : tx_data.active_children) { |
922 | 1.30M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
923 | 1.30M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info0 ; |
924 | 1.30M | } |
925 | 3.55M | } |
926 | 2.24M | SetType bottom_parents, bottom_children; |
927 | 3.54M | for (auto tx_idx : bottom_info.transactions) { |
928 | 3.54M | auto& tx_data = m_tx_data[tx_idx]; |
929 | 3.54M | bottom_parents |= tx_data.parents; |
930 | 3.54M | bottom_children |= tx_data.children; |
931 | 3.54M | for (auto dep_child_idx : tx_data.active_children) { |
932 | 1.30M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
933 | 1.30M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; |
934 | 1.30M | } |
935 | 3.54M | } |
936 | | // Compute the new sets of reachable transactions for each new chunk, based on the |
937 | | // top/bottom parents and children computed above. |
938 | 2.24M | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; |
939 | 2.24M | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; |
940 | 2.24M | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; |
941 | 2.24M | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; |
942 | | // Return the two new set idxs. |
943 | 2.24M | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); |
944 | 2.24M | return {parent_chunk_idx, child_chunk_idx}; |
945 | 2.24M | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 2.24M | { | 892 | 2.24M | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 2.24M | auto& parent_data = m_tx_data[parent_idx]; | 895 | 2.24M | Assume(parent_data.children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 896 | 2.24M | Assume(parent_data.active_children[child_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 897 | | // Get the top set of the active dependency (which will become the parent chunk) and the | 898 | | // chunk set the transactions are currently in (which will become the bottom chunk). | 899 | 2.24M | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 2.24M | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 2.24M | Assume(parent_chunk_idx != child_chunk_idx); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 902 | 2.24M | Assume(m_chunk_idxs[child_chunk_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 903 | 2.24M | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 904 | 2.24M | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 2.24M | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 2.24M | parent_data.active_children.Reset(child_idx); | 909 | 2.24M | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 2.24M | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 2.24M | bottom_info -= top_info; | 913 | | // See the comment above in Activate(). We perform the opposite operations here, removing | 914 | | // instead of adding. Simultaneously, aggregate the top/bottom's union of parents/children. | 915 | 2.24M | SetType top_parents, top_children; | 916 | 3.55M | for (auto tx_idx : top_info.transactions) { | 917 | 3.55M | auto& tx_data = m_tx_data[tx_idx]; | 918 | 3.55M | tx_data.chunk_idx = parent_chunk_idx; | 919 | 3.55M | top_parents |= tx_data.parents; | 920 | 3.55M | top_children |= tx_data.children; | 921 | 3.55M | for (auto dep_child_idx : tx_data.active_children) { | 922 | 1.30M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 1.30M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info0 ; | 924 | 1.30M | } | 925 | 3.55M | } | 926 | 2.24M | SetType bottom_parents, bottom_children; | 927 | 3.54M | for (auto tx_idx : bottom_info.transactions) { | 928 | 3.54M | auto& tx_data = m_tx_data[tx_idx]; | 929 | 3.54M | bottom_parents |= tx_data.parents; | 930 | 3.54M | bottom_children |= tx_data.children; | 931 | 3.54M | for (auto dep_child_idx : tx_data.active_children) { | 932 | 1.30M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 1.30M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 1.30M | } | 935 | 3.54M | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 2.24M | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 2.24M | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 2.24M | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 2.24M | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 2.24M | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 2.24M | return {parent_chunk_idx, child_chunk_idx}; | 945 | 2.24M | } |
|
946 | | |
947 | | /** Activate a dependency from the bottom set to the top set, which must exist. Return the |
948 | | * index of the merged chunk. */ |
949 | | SetIdx MergeChunks(SetIdx top_idx, SetIdx bottom_idx) noexcept |
950 | 2.24M | { |
951 | 2.24M | m_cost.MergeChunksBegin(); |
952 | 2.24M | Assume(m_chunk_idxs[top_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[top_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[top_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
953 | 2.24M | Assume(m_chunk_idxs[bottom_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[bottom_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[bottom_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
954 | 2.24M | auto& top_chunk_info = m_set_info[top_idx]; |
955 | 2.24M | auto& bottom_chunk_info = m_set_info[bottom_idx]; |
956 | | // Count the number of dependencies between bottom_chunk and top_chunk. |
957 | 2.24M | unsigned num_deps{0}; |
958 | 5.71M | for (auto tx_idx : top_chunk_info.transactions) { |
959 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; |
960 | 5.71M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); |
961 | 5.71M | } |
962 | 2.24M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); |
963 | 2.24M | Assume(num_deps > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(num_deps > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(num_deps > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
964 | | // Uniformly randomly pick one of them and activate it. |
965 | 2.24M | unsigned pick = m_rng.randrange(num_deps); |
966 | 2.24M | unsigned num_steps = 0; |
967 | 5.71M | for (auto tx_idx : top_chunk_info.transactions) { |
968 | 5.71M | ++num_steps; |
969 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; |
970 | 5.71M | auto intersect = tx_data.children & bottom_chunk_info.transactions; |
971 | 5.71M | auto count = intersect.Count(); |
972 | 5.71M | if (pick < count) { |
973 | 2.24M | for (auto child_idx : intersect) { |
974 | 2.24M | if (pick == 0) { |
975 | 2.24M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); |
976 | 2.24M | return Activate(tx_idx, child_idx); |
977 | 2.24M | } |
978 | 0 | --pick; |
979 | 0 | } |
980 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
981 | 0 | break; |
982 | 2.24M | } |
983 | 3.47M | pick -= count; |
984 | 3.47M | } |
985 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
986 | 0 | return INVALID_SET_IDX; |
987 | 2.24M | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 2.24M | { | 951 | 2.24M | m_cost.MergeChunksBegin(); | 952 | 2.24M | Assume(m_chunk_idxs[top_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 953 | 2.24M | Assume(m_chunk_idxs[bottom_idx]); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 954 | 2.24M | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 2.24M | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 2.24M | unsigned num_deps{0}; | 958 | 5.71M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 5.71M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 5.71M | } | 962 | 2.24M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 2.24M | Assume(num_deps > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 2.24M | unsigned pick = m_rng.randrange(num_deps); | 966 | 2.24M | unsigned num_steps = 0; | 967 | 5.71M | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 5.71M | ++num_steps; | 969 | 5.71M | auto& tx_data = m_tx_data[tx_idx]; | 970 | 5.71M | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 5.71M | auto count = intersect.Count(); | 972 | 5.71M | if (pick < count) { | 973 | 2.24M | for (auto child_idx : intersect) { | 974 | 2.24M | if (pick == 0) { | 975 | 2.24M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 2.24M | return Activate(tx_idx, child_idx); | 977 | 2.24M | } | 978 | 0 | --pick; | 979 | 0 | } | 980 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 981 | 0 | break; | 982 | 2.24M | } | 983 | 3.47M | pick -= count; | 984 | 3.47M | } | 985 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 986 | 0 | return INVALID_SET_IDX; | 987 | 2.24M | } |
|
988 | | |
989 | | /** Activate a dependency from chunk_idx to merge_chunk_idx (if !DownWard), or a dependency |
990 | | * from merge_chunk_idx to chunk_idx (if DownWard). Return the index of the merged chunk. */ |
991 | | template<bool DownWard> |
992 | | SetIdx MergeChunksDirected(SetIdx chunk_idx, SetIdx merge_chunk_idx) noexcept |
993 | 2.24M | { |
994 | 2.24M | if constexpr (DownWard) { |
995 | 0 | return MergeChunks(chunk_idx, merge_chunk_idx); |
996 | 2.24M | } else { |
997 | 2.24M | return MergeChunks(merge_chunk_idx, chunk_idx); |
998 | 2.24M | } |
999 | 2.24M | } Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 2.24M | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 2.24M | } else { | 997 | 2.24M | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 2.24M | } | 999 | 2.24M | } |
Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) |
1000 | | |
1001 | | /** Determine which chunk to merge chunk_idx with, or INVALID_SET_IDX if none. */ |
1002 | | template<bool DownWard> |
1003 | | SetIdx PickMergeCandidate(SetIdx chunk_idx) noexcept |
1004 | 6.10M | { |
1005 | 6.10M | m_cost.PickMergeCandidateBegin(); |
1006 | | /** Information about the chunk. */ |
1007 | 6.10M | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 5.70M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 396k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1008 | 6.10M | auto& chunk_info = m_set_info[chunk_idx]; |
1009 | | // Iterate over all chunks reachable from this one. For those depended-on chunks, |
1010 | | // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one. |
1011 | | // If multiple equal-feerate candidate chunks to merge with exist, pick a random one |
1012 | | // among them. |
1013 | | |
1014 | | /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when |
1015 | | * looking for candidate chunks to merge with. Initially, this is the original chunk's |
1016 | | * feerate, but is updated to be the current best candidate whenever one is found. */ |
1017 | 6.10M | FeeFrac best_other_chunk_feerate = chunk_info.feerate; |
1018 | | /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */ |
1019 | 6.10M | SetIdx best_other_chunk_idx = INVALID_SET_IDX; |
1020 | | /** We generate random tiebreak values to pick between equal-feerate candidate chunks. |
1021 | | * This variable stores the tiebreak of the current best candidate. */ |
1022 | 6.10M | uint64_t best_other_chunk_tiebreak{0}; |
1023 | | |
1024 | | /** Which parent/child transactions we still need to process the chunks for. */ |
1025 | 6.10M | auto todo = DownWard ? m_reachable[chunk_idx].second396k : m_reachable[chunk_idx].first5.70M ; |
1026 | 6.10M | unsigned steps = 0; |
1027 | 8.34M | while (todo.Any()) { |
1028 | 2.24M | ++steps; |
1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. |
1030 | 2.24M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; |
1031 | 2.24M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; |
1032 | 2.24M | todo -= reached_chunk_info.transactions; |
1033 | | // See if it has an acceptable feerate. |
1034 | 2.24M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate)0 |
1035 | 2.24M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); |
1036 | 2.24M | if (cmp > 0) continue0 ; |
1037 | 2.24M | uint64_t tiebreak = m_rng.rand64(); |
1038 | 2.24M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { |
1039 | 2.24M | best_other_chunk_feerate = reached_chunk_info.feerate; |
1040 | 2.24M | best_other_chunk_idx = reached_chunk_idx; |
1041 | 2.24M | best_other_chunk_tiebreak = tiebreak; |
1042 | 2.24M | } |
1043 | 2.24M | } |
1044 | 6.10M | Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 5.70M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 396k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1045 | | |
1046 | 6.10M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); |
1047 | 6.10M | return best_other_chunk_idx; |
1048 | 6.10M | } Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 5.70M | { | 1005 | 5.70M | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 5.70M | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 5.70M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1008 | 5.70M | auto& chunk_info = m_set_info[chunk_idx]; | 1009 | | // Iterate over all chunks reachable from this one. For those depended-on chunks, | 1010 | | // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one. | 1011 | | // If multiple equal-feerate candidate chunks to merge with exist, pick a random one | 1012 | | // among them. | 1013 | | | 1014 | | /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when | 1015 | | * looking for candidate chunks to merge with. Initially, this is the original chunk's | 1016 | | * feerate, but is updated to be the current best candidate whenever one is found. */ | 1017 | 5.70M | FeeFrac best_other_chunk_feerate = chunk_info.feerate; | 1018 | | /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */ | 1019 | 5.70M | SetIdx best_other_chunk_idx = INVALID_SET_IDX; | 1020 | | /** We generate random tiebreak values to pick between equal-feerate candidate chunks. | 1021 | | * This variable stores the tiebreak of the current best candidate. */ | 1022 | 5.70M | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 5.70M | auto todo = DownWard ? m_reachable[chunk_idx].second0 : m_reachable[chunk_idx].first; | 1026 | 5.70M | unsigned steps = 0; | 1027 | 7.95M | while (todo.Any()) { | 1028 | 2.24M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 2.24M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 2.24M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 2.24M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 2.24M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate)0 | 1035 | 2.24M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 2.24M | if (cmp > 0) continue0 ; | 1037 | 2.24M | uint64_t tiebreak = m_rng.rand64(); | 1038 | 2.24M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 2.24M | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 2.24M | best_other_chunk_idx = reached_chunk_idx; | 1041 | 2.24M | best_other_chunk_tiebreak = tiebreak; | 1042 | 2.24M | } | 1043 | 2.24M | } | 1044 | 5.70M | Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 5.70M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1045 | | | 1046 | 5.70M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 5.70M | return best_other_chunk_idx; | 1048 | 5.70M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 396k | { | 1005 | 396k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 396k | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 396k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1008 | 396k | auto& chunk_info = m_set_info[chunk_idx]; | 1009 | | // Iterate over all chunks reachable from this one. For those depended-on chunks, | 1010 | | // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one. | 1011 | | // If multiple equal-feerate candidate chunks to merge with exist, pick a random one | 1012 | | // among them. | 1013 | | | 1014 | | /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when | 1015 | | * looking for candidate chunks to merge with. Initially, this is the original chunk's | 1016 | | * feerate, but is updated to be the current best candidate whenever one is found. */ | 1017 | 396k | FeeFrac best_other_chunk_feerate = chunk_info.feerate; | 1018 | | /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */ | 1019 | 396k | SetIdx best_other_chunk_idx = INVALID_SET_IDX; | 1020 | | /** We generate random tiebreak values to pick between equal-feerate candidate chunks. | 1021 | | * This variable stores the tiebreak of the current best candidate. */ | 1022 | 396k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 396k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first0 ; | 1026 | 396k | unsigned steps = 0; | 1027 | 396k | while (todo.Any()) { | 1028 | 0 | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 0 | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 0 | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 0 | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 0 | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 0 | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 0 | if (cmp > 0) continue; | 1037 | 0 | uint64_t tiebreak = m_rng.rand64(); | 1038 | 0 | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 0 | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 0 | best_other_chunk_idx = reached_chunk_idx; | 1041 | 0 | best_other_chunk_tiebreak = tiebreak; | 1042 | 0 | } | 1043 | 0 | } | 1044 | 396k | Assume(steps <= m_set_info.size()); Line | Count | Source | 125 | 396k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1045 | | | 1046 | 396k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 396k | return best_other_chunk_idx; | 1048 | 396k | } |
|
1049 | | |
1050 | | /** Perform an upward or downward merge step, on the specified chunk. Returns the merged chunk, |
1051 | | * or INVALID_SET_IDX if no merge took place. */ |
1052 | | template<bool DownWard> |
1053 | | SetIdx MergeStep(SetIdx chunk_idx) noexcept |
1054 | 6.10M | { |
1055 | 6.10M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); |
1056 | 6.10M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX3.85M ; |
1057 | 2.24M | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); |
1058 | 2.24M | Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1059 | 2.24M | return chunk_idx; |
1060 | 6.10M | } Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Unexecuted instantiation: unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 5.70M | { | 1055 | 5.70M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 5.70M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX3.45M ; | 1057 | 2.24M | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 2.24M | Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1059 | 2.24M | return chunk_idx; | 1060 | 5.70M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 396k | { | 1055 | 396k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 396k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 0 | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 0 | Assume(chunk_idx != INVALID_SET_IDX); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1059 | 0 | return chunk_idx; | 1060 | 396k | } |
|
1061 | | |
1062 | | /** Perform an upward or downward merge sequence on the specified chunk. */ |
1063 | | template<bool DownWard> |
1064 | | void MergeSequence(SetIdx chunk_idx) noexcept |
1065 | 0 | { |
1066 | 0 | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1067 | 0 | while (true) { |
1068 | 0 | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); |
1069 | 0 | if (merged_chunk_idx == INVALID_SET_IDX) break; |
1070 | 0 | chunk_idx = merged_chunk_idx; |
1071 | 0 | } |
1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. |
1073 | 0 | if (!m_suboptimal_idxs[chunk_idx]) { |
1074 | 0 | m_suboptimal_idxs.Set(chunk_idx); |
1075 | 0 | m_suboptimal_chunks.push_back(chunk_idx); |
1076 | 0 | } |
1077 | 0 | } Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Unexecuted instantiation: void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) |
1078 | | |
1079 | | /** Split a chunk, and then merge the resulting two chunks to make the graph topological |
1080 | | * again. */ |
1081 | | void Improve(TxIdx parent_idx, TxIdx child_idx) noexcept |
1082 | 0 | { |
1083 | | // Deactivate the specified dependency, splitting it into two new chunks: a top containing |
1084 | | // the parent, and a bottom containing the child. The top should have a higher feerate. |
1085 | 0 | auto [parent_chunk_idx, child_chunk_idx] = Deactivate(parent_idx, child_idx); |
1086 | | |
1087 | | // At this point we have exactly two chunks which may violate topology constraints (the |
1088 | | // parent chunk and child chunk that were produced by deactivation). We can fix |
1089 | | // these using just merge sequences, one upwards and one downwards, avoiding the need for a |
1090 | | // full MakeTopological. |
1091 | 0 | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; |
1092 | 0 | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; |
1093 | 0 | if (parent_reachable.Overlaps(child_chunk_txn)) { |
1094 | | // The parent chunk has a dependency on a transaction in the child chunk. In this case, |
1095 | | // the parent needs to merge back with the child chunk (a self-merge), and no other |
1096 | | // merges are needed. Special-case this, so the overhead of PickMergeCandidate and |
1097 | | // MergeSequence can be avoided. |
1098 | | |
1099 | | // In the self-merge, the roles reverse: the parent chunk (from the split) depends |
1100 | | // on the child chunk, so child_chunk_idx is the "top" and parent_chunk_idx is the |
1101 | | // "bottom" for MergeChunks. |
1102 | 0 | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); |
1103 | 0 | if (!m_suboptimal_idxs[merged_chunk_idx]) { |
1104 | 0 | m_suboptimal_idxs.Set(merged_chunk_idx); |
1105 | 0 | m_suboptimal_chunks.push_back(merged_chunk_idx); |
1106 | 0 | } |
1107 | 0 | } else { |
1108 | | // Merge the top chunk with lower-feerate chunks it depends on. |
1109 | 0 | MergeSequence<false>(parent_chunk_idx); |
1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. |
1111 | 0 | MergeSequence<true>(child_chunk_idx); |
1112 | 0 | } |
1113 | 0 | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) |
1114 | | |
1115 | | /** Determine the next chunk to optimize, or INVALID_SET_IDX if none. */ |
1116 | | SetIdx PickChunkToOptimize() noexcept |
1117 | 821k | { |
1118 | 821k | m_cost.PickChunkToOptimizeBegin(); |
1119 | 821k | unsigned steps{0}; |
1120 | 821k | while (!m_suboptimal_chunks.empty()) { |
1121 | 821k | ++steps; |
1122 | | // Pop an entry from the potentially-suboptimal chunk queue. |
1123 | 821k | SetIdx chunk_idx = m_suboptimal_chunks.front(); |
1124 | 821k | Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1125 | 821k | m_suboptimal_idxs.Reset(chunk_idx); |
1126 | 821k | m_suboptimal_chunks.pop_front(); |
1127 | 821k | if (m_chunk_idxs[chunk_idx]) { |
1128 | 821k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); |
1129 | 821k | return chunk_idx; |
1130 | 821k | } |
1131 | | // If what was popped is not currently a chunk, continue. This may |
1132 | | // happen when a split chunk merges in Improve() with one or more existing chunks that |
1133 | | // are themselves on the suboptimal queue already. |
1134 | 821k | } |
1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); |
1136 | 0 | return INVALID_SET_IDX; |
1137 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 821k | { | 1118 | 821k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 821k | unsigned steps{0}; | 1120 | 821k | while (!m_suboptimal_chunks.empty()) { | 1121 | 821k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 821k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 821k | Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1125 | 821k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 821k | m_suboptimal_chunks.pop_front(); | 1127 | 821k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 821k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 821k | return chunk_idx; | 1130 | 821k | } | 1131 | | // If what was popped is not currently a chunk, continue. This may | 1132 | | // happen when a split chunk merges in Improve() with one or more existing chunks that | 1133 | | // are themselves on the suboptimal queue already. | 1134 | 821k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 821k | } |
|
1138 | | |
1139 | | /** Find a (parent, child) dependency to deactivate in chunk_idx, or (-1, -1) if none. */ |
1140 | | std::pair<TxIdx, TxIdx> PickDependencyToSplit(SetIdx chunk_idx) noexcept |
1141 | 821k | { |
1142 | 821k | m_cost.PickDependencyToSplitBegin(); |
1143 | 821k | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1144 | 821k | auto& chunk_info = m_set_info[chunk_idx]; |
1145 | | |
1146 | | // Remember the best dependency {par, chl} seen so far. |
1147 | 821k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; |
1148 | 821k | uint64_t candidate_tiebreak = 0; |
1149 | | // Iterate over all transactions. |
1150 | 3.06M | for (auto tx_idx : chunk_info.transactions) { |
1151 | 3.06M | const auto& tx_data = m_tx_data[tx_idx]; |
1152 | | // Iterate over all active child dependencies of the transaction. |
1153 | 3.06M | for (auto child_idx : tx_data.active_children) { |
1154 | 2.24M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]]; |
1155 | | // Skip if this dependency is ineligible (the top chunk that would be created |
1156 | | // does not have higher feerate than the chunk it is currently part of). |
1157 | 2.24M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); |
1158 | 2.24M | if (cmp <= 0) continue; |
1159 | | // Generate a random tiebreak for this dependency, and reject it if its tiebreak |
1160 | | // is worse than the best so far. This means that among all eligible |
1161 | | // dependencies, a uniformly random one will be chosen. |
1162 | 0 | uint64_t tiebreak = m_rng.rand64(); |
1163 | 0 | if (tiebreak < candidate_tiebreak) continue; |
1164 | | // Remember this as our (new) candidate dependency. |
1165 | 0 | candidate_dep = {tx_idx, child_idx}; |
1166 | 0 | candidate_tiebreak = tiebreak; |
1167 | 0 | } |
1168 | 3.06M | } |
1169 | 821k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); |
1170 | 821k | return candidate_dep; |
1171 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 821k | { | 1142 | 821k | m_cost.PickDependencyToSplitBegin(); | 1143 | 821k | Assume(m_chunk_idxs[chunk_idx]); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1144 | 821k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 821k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 821k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 3.06M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 3.06M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 3.06M | for (auto child_idx : tx_data.active_children) { | 1154 | 2.24M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]]; | 1155 | | // Skip if this dependency is ineligible (the top chunk that would be created | 1156 | | // does not have higher feerate than the chunk it is currently part of). | 1157 | 2.24M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 2.24M | if (cmp <= 0) continue; | 1159 | | // Generate a random tiebreak for this dependency, and reject it if its tiebreak | 1160 | | // is worse than the best so far. This means that among all eligible | 1161 | | // dependencies, a uniformly random one will be chosen. | 1162 | 0 | uint64_t tiebreak = m_rng.rand64(); | 1163 | 0 | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 0 | candidate_dep = {tx_idx, child_idx}; | 1166 | 0 | candidate_tiebreak = tiebreak; | 1167 | 0 | } | 1168 | 3.06M | } | 1169 | 821k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 821k | return candidate_dep; | 1171 | 821k | } |
|
1172 | | |
1173 | | public: |
1174 | | /** Construct a spanning forest for the given DepGraph, with every transaction in its own chunk |
1175 | | * (not topological). */ |
1176 | | explicit SpanningForestState(const DepGraph<SetType>& depgraph LIFETIMEBOUND, uint64_t rng_seed, const CostModel& cost = CostModel{}) noexcept : |
1177 | 821k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) |
1178 | 821k | { |
1179 | 821k | m_cost.InitializeBegin(); |
1180 | 821k | m_transaction_idxs = depgraph.Positions(); |
1181 | 821k | auto num_transactions = m_transaction_idxs.Count(); |
1182 | 821k | m_tx_data.resize(depgraph.PositionRange()); |
1183 | 821k | m_set_info.resize(num_transactions); |
1184 | 821k | m_reachable.resize(num_transactions); |
1185 | 821k | size_t num_chunks = 0; |
1186 | 821k | size_t num_deps = 0; |
1187 | 3.06M | for (auto tx_idx : m_transaction_idxs) { |
1188 | | // Fill in transaction data. |
1189 | 3.06M | auto& tx_data = m_tx_data[tx_idx]; |
1190 | 3.06M | tx_data.parents = depgraph.GetReducedParents(tx_idx); |
1191 | 3.06M | for (auto parent_idx : tx_data.parents) { |
1192 | 2.24M | m_tx_data[parent_idx].children.Set(tx_idx); |
1193 | 2.24M | } |
1194 | 3.06M | num_deps += tx_data.parents.Count(); |
1195 | | // Create a singleton chunk for it. |
1196 | 3.06M | tx_data.chunk_idx = num_chunks; |
1197 | 3.06M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); |
1198 | 3.06M | } |
1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. |
1200 | 3.89M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx3.06M ) { |
1201 | 3.06M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; |
1202 | 3.06M | m_reachable[chunk_idx].first = tx_data.parents; |
1203 | 3.06M | m_reachable[chunk_idx].second = tx_data.children; |
1204 | 3.06M | } |
1205 | 821k | Assume(num_chunks == num_transactions); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(num_chunks == num_transactions); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(num_chunks == num_transactions); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1206 | | // Mark all chunk sets as chunks. |
1207 | 821k | m_chunk_idxs = SetType::Fill(num_chunks); |
1208 | 821k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); |
1209 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) Line | Count | Source | 1177 | 821k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 821k | { | 1179 | 821k | m_cost.InitializeBegin(); | 1180 | 821k | m_transaction_idxs = depgraph.Positions(); | 1181 | 821k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 821k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 821k | m_set_info.resize(num_transactions); | 1184 | 821k | m_reachable.resize(num_transactions); | 1185 | 821k | size_t num_chunks = 0; | 1186 | 821k | size_t num_deps = 0; | 1187 | 3.06M | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 3.06M | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 3.06M | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 3.06M | for (auto parent_idx : tx_data.parents) { | 1192 | 2.24M | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 2.24M | } | 1194 | 3.06M | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 3.06M | tx_data.chunk_idx = num_chunks; | 1197 | 3.06M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 3.06M | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 3.89M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx3.06M ) { | 1201 | 3.06M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 3.06M | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 3.06M | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 3.06M | } | 1205 | 821k | Assume(num_chunks == num_transactions); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1206 | | // Mark all chunk sets as chunks. | 1207 | 821k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 821k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 821k | } |
|
1210 | | |
1211 | | /** Load an existing linearization. Must be called immediately after constructor. The result is |
1212 | | * topological if the linearization is valid. Otherwise, MakeTopological still needs to be |
1213 | | * called. */ |
1214 | | void LoadLinearization(std::span<const DepGraphIndex> old_linearization) noexcept |
1215 | 821k | { |
1216 | | // Add transactions one by one, in order of existing linearization. |
1217 | 3.06M | for (DepGraphIndex tx_idx : old_linearization) { |
1218 | 3.06M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; |
1219 | | // Merge the chunk upwards, as long as merging succeeds. |
1220 | 5.31M | while (true) { |
1221 | 5.31M | chunk_idx = MergeStep<false>(chunk_idx); |
1222 | 5.31M | if (chunk_idx == INVALID_SET_IDX) break3.06M ; |
1223 | 5.31M | } |
1224 | 3.06M | } |
1225 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 821k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 3.06M | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 3.06M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 5.31M | while (true) { | 1221 | 5.31M | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 5.31M | if (chunk_idx == INVALID_SET_IDX) break3.06M ; | 1223 | 5.31M | } | 1224 | 3.06M | } | 1225 | 821k | } |
|
1226 | | |
1227 | | /** Make state topological. Can be called after constructing, or after LoadLinearization. */ |
1228 | | void MakeTopological() noexcept |
1229 | 784k | { |
1230 | 784k | m_cost.MakeTopologicalBegin(); |
1231 | 784k | Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 784k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1232 | | /** What direction to initially merge chunks in; one of the two directions is enough. This |
1233 | | * is sufficient because if a non-topological inactive dependency exists between two |
1234 | | * chunks, at least one of the two chunks will eventually be processed in a direction that |
1235 | | * discovers it - either the lower chunk tries upward, or the upper chunk tries downward. |
1236 | | * Chunks that are the result of the merging are always tried in both directions. */ |
1237 | 784k | unsigned init_dir = m_rng.randbool(); |
1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both |
1239 | | * directions. */ |
1240 | 784k | SetType merged_chunks; |
1241 | | // Mark chunks as suboptimal. |
1242 | 784k | m_suboptimal_idxs = m_chunk_idxs; |
1243 | 784k | for (auto chunk_idx : m_chunk_idxs) { |
1244 | 784k | m_suboptimal_chunks.emplace_back(chunk_idx); |
1245 | | // Randomize the initial order of suboptimal chunks in the queue. |
1246 | 784k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); |
1247 | 784k | if (j != m_suboptimal_chunks.size() - 1) { |
1248 | 0 | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); |
1249 | 0 | } |
1250 | 784k | } |
1251 | 784k | unsigned chunks = m_chunk_idxs.Count(); |
1252 | 784k | unsigned steps = 0; |
1253 | 1.56M | while (!m_suboptimal_chunks.empty()) { |
1254 | 784k | ++steps; |
1255 | | // Pop an entry from the potentially-suboptimal chunk queue. |
1256 | 784k | SetIdx chunk_idx = m_suboptimal_chunks.front(); |
1257 | 784k | m_suboptimal_chunks.pop_front(); |
1258 | 784k | Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 784k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1259 | 784k | m_suboptimal_idxs.Reset(chunk_idx); |
1260 | | // If what was popped is not currently a chunk, continue. This may |
1261 | | // happen when it was merged with something else since being added. |
1262 | 784k | if (!m_chunk_idxs[chunk_idx]) continue0 ; |
1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ |
1264 | 784k | unsigned direction = merged_chunks[chunk_idx] ? 30 : init_dir + 1; |
1265 | 784k | int flip = m_rng.randbool(); |
1266 | 2.35M | for (int i = 0; i < 2; ++i1.56M ) { |
1267 | 1.56M | if (i ^ flip) { |
1268 | 784k | if (!(direction & 1)) continue396k ; |
1269 | | // Attempt to merge the chunk upwards. |
1270 | 388k | auto result_up = MergeStep<false>(chunk_idx); |
1271 | 388k | if (result_up != INVALID_SET_IDX) { |
1272 | 0 | if (!m_suboptimal_idxs[result_up]) { |
1273 | 0 | m_suboptimal_idxs.Set(result_up); |
1274 | 0 | m_suboptimal_chunks.push_back(result_up); |
1275 | 0 | } |
1276 | 0 | merged_chunks.Set(result_up); |
1277 | 0 | break; |
1278 | 0 | } |
1279 | 784k | } else { |
1280 | 784k | if (!(direction & 2)) continue388k ; |
1281 | | // Attempt to merge the chunk downwards. |
1282 | 396k | auto result_down = MergeStep<true>(chunk_idx); |
1283 | 396k | if (result_down != INVALID_SET_IDX) { |
1284 | 0 | if (!m_suboptimal_idxs[result_down]) { |
1285 | 0 | m_suboptimal_idxs.Set(result_down); |
1286 | 0 | m_suboptimal_chunks.push_back(result_down); |
1287 | 0 | } |
1288 | 0 | merged_chunks.Set(result_down); |
1289 | 0 | break; |
1290 | 0 | } |
1291 | 396k | } |
1292 | 1.56M | } |
1293 | 784k | } |
1294 | 784k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); |
1295 | 784k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 784k | { | 1230 | 784k | m_cost.MakeTopologicalBegin(); | 1231 | 784k | Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 784k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1232 | | /** What direction to initially merge chunks in; one of the two directions is enough. This | 1233 | | * is sufficient because if a non-topological inactive dependency exists between two | 1234 | | * chunks, at least one of the two chunks will eventually be processed in a direction that | 1235 | | * discovers it - either the lower chunk tries upward, or the upper chunk tries downward. | 1236 | | * Chunks that are the result of the merging are always tried in both directions. */ | 1237 | 784k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 784k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 784k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 784k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 784k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 784k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 784k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 0 | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 0 | } | 1250 | 784k | } | 1251 | 784k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 784k | unsigned steps = 0; | 1253 | 1.56M | while (!m_suboptimal_chunks.empty()) { | 1254 | 784k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 784k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 784k | m_suboptimal_chunks.pop_front(); | 1258 | 784k | Assume(m_suboptimal_idxs[chunk_idx]); Line | Count | Source | 125 | 784k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1259 | 784k | m_suboptimal_idxs.Reset(chunk_idx); | 1260 | | // If what was popped is not currently a chunk, continue. This may | 1261 | | // happen when it was merged with something else since being added. | 1262 | 784k | if (!m_chunk_idxs[chunk_idx]) continue0 ; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 784k | unsigned direction = merged_chunks[chunk_idx] ? 30 : init_dir + 1; | 1265 | 784k | int flip = m_rng.randbool(); | 1266 | 2.35M | for (int i = 0; i < 2; ++i1.56M ) { | 1267 | 1.56M | if (i ^ flip) { | 1268 | 784k | if (!(direction & 1)) continue396k ; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 388k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 388k | if (result_up != INVALID_SET_IDX) { | 1272 | 0 | if (!m_suboptimal_idxs[result_up]) { | 1273 | 0 | m_suboptimal_idxs.Set(result_up); | 1274 | 0 | m_suboptimal_chunks.push_back(result_up); | 1275 | 0 | } | 1276 | 0 | merged_chunks.Set(result_up); | 1277 | 0 | break; | 1278 | 0 | } | 1279 | 784k | } else { | 1280 | 784k | if (!(direction & 2)) continue388k ; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 396k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 396k | if (result_down != INVALID_SET_IDX) { | 1284 | 0 | if (!m_suboptimal_idxs[result_down]) { | 1285 | 0 | m_suboptimal_idxs.Set(result_down); | 1286 | 0 | m_suboptimal_chunks.push_back(result_down); | 1287 | 0 | } | 1288 | 0 | merged_chunks.Set(result_down); | 1289 | 0 | break; | 1290 | 0 | } | 1291 | 396k | } | 1292 | 1.56M | } | 1293 | 784k | } | 1294 | 784k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 784k | } |
|
1296 | | |
1297 | | /** Initialize the data structure for optimization. It must be topological already. */ |
1298 | | void StartOptimizing() noexcept |
1299 | 821k | { |
1300 | 821k | m_cost.StartOptimizingBegin(); |
1301 | 821k | Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1302 | | // Mark chunks suboptimal. |
1303 | 821k | m_suboptimal_idxs = m_chunk_idxs; |
1304 | 821k | for (auto chunk_idx : m_chunk_idxs) { |
1305 | 821k | m_suboptimal_chunks.push_back(chunk_idx); |
1306 | | // Randomize the initial order of suboptimal chunks in the queue. |
1307 | 821k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); |
1308 | 821k | if (j != m_suboptimal_chunks.size() - 1) { |
1309 | 0 | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); |
1310 | 0 | } |
1311 | 821k | } |
1312 | 821k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); |
1313 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 821k | { | 1300 | 821k | m_cost.StartOptimizingBegin(); | 1301 | 821k | Assume(m_suboptimal_chunks.empty()); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1302 | | // Mark chunks suboptimal. | 1303 | 821k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 821k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 821k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 821k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 821k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 0 | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 0 | } | 1311 | 821k | } | 1312 | 821k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 821k | } |
|
1314 | | |
1315 | | /** Try to improve the forest. Returns false if it is optimal, true otherwise. */ |
1316 | | bool OptimizeStep() noexcept |
1317 | 821k | { |
1318 | 821k | auto chunk_idx = PickChunkToOptimize(); |
1319 | 821k | if (chunk_idx == INVALID_SET_IDX) { |
1320 | | // No improvable chunk was found, we are done. |
1321 | 0 | return false; |
1322 | 0 | } |
1323 | 821k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); |
1324 | 821k | if (parent_idx == TxIdx(-1)) { |
1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. |
1326 | 821k | return !m_suboptimal_chunks.empty(); |
1327 | 821k | } |
1328 | | // Deactivate the found dependency and then make the state topological again with a |
1329 | | // sequence of merges. |
1330 | 0 | Improve(parent_idx, child_idx); |
1331 | 0 | return true; |
1332 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 821k | { | 1318 | 821k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 821k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 821k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 821k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 821k | return !m_suboptimal_chunks.empty(); | 1327 | 821k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 0 | Improve(parent_idx, child_idx); | 1331 | 0 | return true; | 1332 | 821k | } |
|
1333 | | |
1334 | | /** Initialize data structure for minimizing the chunks. Can only be called if state is known |
1335 | | * to be optimal. OptimizeStep() cannot be called anymore afterwards. */ |
1336 | | void StartMinimizing() noexcept |
1337 | 821k | { |
1338 | 821k | m_cost.StartMinimizingBegin(); |
1339 | 821k | m_nonminimal_chunks.clear(); |
1340 | 821k | m_nonminimal_chunks.reserve(m_transaction_idxs.Count()); |
1341 | | // Gather all chunks, and for each, add it with a random pivot in it, and a random initial |
1342 | | // direction, to m_nonminimal_chunks. |
1343 | 821k | for (auto chunk_idx : m_chunk_idxs) { |
1344 | 821k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); |
1345 | 821k | m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>()); |
1346 | | // Randomize the initial order of nonminimal chunks in the queue. |
1347 | 821k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); |
1348 | 821k | if (j != m_nonminimal_chunks.size() - 1) { |
1349 | 0 | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); |
1350 | 0 | } |
1351 | 821k | } |
1352 | 821k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); |
1353 | 821k | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 821k | { | 1338 | 821k | m_cost.StartMinimizingBegin(); | 1339 | 821k | m_nonminimal_chunks.clear(); | 1340 | 821k | m_nonminimal_chunks.reserve(m_transaction_idxs.Count()); | 1341 | | // Gather all chunks, and for each, add it with a random pivot in it, and a random initial | 1342 | | // direction, to m_nonminimal_chunks. | 1343 | 821k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 821k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 821k | m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>()); | 1346 | | // Randomize the initial order of nonminimal chunks in the queue. | 1347 | 821k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 821k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 0 | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 0 | } | 1351 | 821k | } | 1352 | 821k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 821k | } |
|
1354 | | |
1355 | | /** Try to reduce a chunk's size. Returns false if all chunks are minimal, true otherwise. */ |
1356 | | bool MinimizeStep() noexcept |
1357 | 6.99M | { |
1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. |
1359 | 6.99M | if (m_nonminimal_chunks.empty()) return false821k ; |
1360 | 6.16M | m_cost.MinimizeStepBegin(); |
1361 | | // Pop an entry from the potentially-non-minimal chunk queue. |
1362 | 6.16M | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); |
1363 | 6.16M | m_nonminimal_chunks.pop_front(); |
1364 | 6.16M | auto& chunk_info = m_set_info[chunk_idx]; |
1365 | | /** Whether to move the pivot down rather than up. */ |
1366 | 6.16M | bool move_pivot_down = flags & 1; |
1367 | | /** Whether this is already the second stage. */ |
1368 | 6.16M | bool second_stage = flags & 2; |
1369 | | |
1370 | | // Find a random dependency whose top and bottom set feerates are equal, and which has |
1371 | | // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down). |
1372 | 6.16M | std::pair<TxIdx, TxIdx> candidate_dep; |
1373 | 6.16M | uint64_t candidate_tiebreak{0}; |
1374 | 6.16M | bool have_any = false; |
1375 | | // Iterate over all transactions. |
1376 | 12.6M | for (auto tx_idx : chunk_info.transactions) { |
1377 | 12.6M | const auto& tx_data = m_tx_data[tx_idx]; |
1378 | | // Iterate over all active child dependencies of the transaction. |
1379 | 12.6M | for (auto child_idx : tx_data.active_children) { |
1380 | 6.43M | const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]]; |
1381 | | // Skip if this dependency does not have equal top and bottom set feerates. Note |
1382 | | // that the top cannot have higher feerate than the bottom, or OptimizeSteps would |
1383 | | // have dealt with it. |
1384 | 6.43M | if (dep_top_info.feerate << chunk_info.feerate) continue0 ; |
1385 | 6.43M | have_any = true; |
1386 | | // Skip if this dependency does not have pivot in the right place. |
1387 | 6.43M | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue2.55M ; |
1388 | | // Remember this as our chosen dependency if it has a better tiebreak. |
1389 | 3.88M | uint64_t tiebreak = m_rng.rand64() | 1; |
1390 | 3.88M | if (tiebreak > candidate_tiebreak) { |
1391 | 2.89M | candidate_tiebreak = tiebreak; |
1392 | 2.89M | candidate_dep = {tx_idx, child_idx}; |
1393 | 2.89M | } |
1394 | 3.88M | } |
1395 | 12.6M | } |
1396 | 6.16M | m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count()); |
1397 | | // If no dependencies have equal top and bottom set feerate, this chunk is minimal. |
1398 | 6.16M | if (!have_any) return true3.06M ; |
1399 | | // If all found dependencies have the pivot in the wrong place, try moving it in the other |
1400 | | // direction. If this was the second stage already, we are done. |
1401 | 3.10M | if (candidate_tiebreak == 0) { |
1402 | | // Switch to other direction, and to second phase. |
1403 | 852k | flags ^= 3; |
1404 | 852k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); |
1405 | 852k | return true; |
1406 | 852k | } |
1407 | | |
1408 | | // Otherwise, deactivate the dependency that was found. |
1409 | 2.24M | auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second); |
1410 | | // Determine if there is a dependency from the new bottom to the new top (opposite from the |
1411 | | // dependency that was just deactivated). |
1412 | 2.24M | auto& parent_reachable = m_reachable[parent_chunk_idx].first; |
1413 | 2.24M | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; |
1414 | 2.24M | if (parent_reachable.Overlaps(child_chunk_txn)) { |
1415 | | // A self-merge is needed. Note that the child_chunk_idx is the top, and |
1416 | | // parent_chunk_idx is the bottom, because we activate a dependency in the reverse |
1417 | | // direction compared to the deactivation above. |
1418 | 0 | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); |
1419 | | // Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx |
1420 | | // will have changed. |
1421 | 0 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); |
1422 | 0 | m_cost.MinimizeStepEnd(/*split=*/false); |
1423 | 2.24M | } else { |
1424 | | // No self-merge happens, and thus we have found a way to split the chunk. Create two |
1425 | | // smaller chunks, and add them to the queue. The one that contains the current pivot |
1426 | | // gets to continue with it in the same direction, to minimize the number of times we |
1427 | | // alternate direction. If we were in the second phase already, the newly created chunk |
1428 | | // inherits that too, because we know no split with the pivot on the other side is |
1429 | | // possible already. The new chunk without the current pivot gets a new randomly-chosen |
1430 | | // one. |
1431 | 2.24M | if (move_pivot_down) { |
1432 | 1.12M | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); |
1433 | 1.12M | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); |
1434 | 1.12M | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); |
1435 | 1.12M | } else { |
1436 | 1.12M | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); |
1437 | 1.12M | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); |
1438 | 1.12M | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); |
1439 | 1.12M | } |
1440 | 2.24M | if (m_rng.randbool()) { |
1441 | 1.08M | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); |
1442 | 1.08M | } |
1443 | 2.24M | m_cost.MinimizeStepEnd(/*split=*/true); |
1444 | 2.24M | } |
1445 | 2.24M | return true; |
1446 | 3.10M | } Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 6.99M | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 6.99M | if (m_nonminimal_chunks.empty()) return false821k ; | 1360 | 6.16M | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 6.16M | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 6.16M | m_nonminimal_chunks.pop_front(); | 1364 | 6.16M | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 6.16M | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 6.16M | bool second_stage = flags & 2; | 1369 | | | 1370 | | // Find a random dependency whose top and bottom set feerates are equal, and which has | 1371 | | // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down). | 1372 | 6.16M | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 6.16M | uint64_t candidate_tiebreak{0}; | 1374 | 6.16M | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 12.6M | for (auto tx_idx : chunk_info.transactions) { | 1377 | 12.6M | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 12.6M | for (auto child_idx : tx_data.active_children) { | 1380 | 6.43M | const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]]; | 1381 | | // Skip if this dependency does not have equal top and bottom set feerates. Note | 1382 | | // that the top cannot have higher feerate than the bottom, or OptimizeSteps would | 1383 | | // have dealt with it. | 1384 | 6.43M | if (dep_top_info.feerate << chunk_info.feerate) continue0 ; | 1385 | 6.43M | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 6.43M | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue2.55M ; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 3.88M | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 3.88M | if (tiebreak > candidate_tiebreak) { | 1391 | 2.89M | candidate_tiebreak = tiebreak; | 1392 | 2.89M | candidate_dep = {tx_idx, child_idx}; | 1393 | 2.89M | } | 1394 | 3.88M | } | 1395 | 12.6M | } | 1396 | 6.16M | m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count()); | 1397 | | // If no dependencies have equal top and bottom set feerate, this chunk is minimal. | 1398 | 6.16M | if (!have_any) return true3.06M ; | 1399 | | // If all found dependencies have the pivot in the wrong place, try moving it in the other | 1400 | | // direction. If this was the second stage already, we are done. | 1401 | 3.10M | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 852k | flags ^= 3; | 1404 | 852k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 852k | return true; | 1406 | 852k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 2.24M | auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second); | 1410 | | // Determine if there is a dependency from the new bottom to the new top (opposite from the | 1411 | | // dependency that was just deactivated). | 1412 | 2.24M | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 2.24M | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 2.24M | if (parent_reachable.Overlaps(child_chunk_txn)) { | 1415 | | // A self-merge is needed. Note that the child_chunk_idx is the top, and | 1416 | | // parent_chunk_idx is the bottom, because we activate a dependency in the reverse | 1417 | | // direction compared to the deactivation above. | 1418 | 0 | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1419 | | // Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx | 1420 | | // will have changed. | 1421 | 0 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); | 1422 | 0 | m_cost.MinimizeStepEnd(/*split=*/false); | 1423 | 2.24M | } else { | 1424 | | // No self-merge happens, and thus we have found a way to split the chunk. Create two | 1425 | | // smaller chunks, and add them to the queue. The one that contains the current pivot | 1426 | | // gets to continue with it in the same direction, to minimize the number of times we | 1427 | | // alternate direction. If we were in the second phase already, the newly created chunk | 1428 | | // inherits that too, because we know no split with the pivot on the other side is | 1429 | | // possible already. The new chunk without the current pivot gets a new randomly-chosen | 1430 | | // one. | 1431 | 2.24M | if (move_pivot_down) { | 1432 | 1.12M | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 1.12M | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 1.12M | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 1.12M | } else { | 1436 | 1.12M | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 1.12M | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 1.12M | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 1.12M | } | 1440 | 2.24M | if (m_rng.randbool()) { | 1441 | 1.08M | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 1.08M | } | 1443 | 2.24M | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 2.24M | } | 1445 | 2.24M | return true; | 1446 | 3.10M | } |
|
1447 | | |
1448 | | /** Construct a topologically-valid linearization from the current forest state. Must be |
1449 | | * topological. fallback_order is a comparator that defines a strong order for DepGraphIndexes |
1450 | | * in this cluster, used to order equal-feerate transactions and chunks. |
1451 | | * |
1452 | | * Specifically, the resulting order consists of: |
1453 | | * - The chunks of the current SFL state, sorted by (in decreasing order of priority): |
1454 | | * - topology (parents before children) |
1455 | | * - highest chunk feerate first |
1456 | | * - smallest chunk size first |
1457 | | * - the chunk with the lowest maximum transaction, by fallback_order, first |
1458 | | * - The transactions within a chunk, sorted by (in decreasing order of priority): |
1459 | | * - topology (parents before children) |
1460 | | * - highest tx feerate first |
1461 | | * - smallest tx size first |
1462 | | * - the lowest transaction, by fallback_order, first |
1463 | | */ |
1464 | | std::vector<DepGraphIndex> GetLinearization(const StrongComparator<DepGraphIndex> auto& fallback_order) noexcept |
1465 | 821k | { |
1466 | 821k | m_cost.GetLinearizationBegin(); |
1467 | | /** The output linearization. */ |
1468 | 821k | std::vector<DepGraphIndex> ret; |
1469 | 821k | ret.reserve(m_set_info.size()); |
1470 | | /** A heap with all chunks (by set index) that can currently be included, sorted by |
1471 | | * chunk feerate (high to low), chunk size (small to large), and by least maximum element |
1472 | | * according to the fallback order (which is the second pair element). */ |
1473 | 821k | std::vector<std::pair<SetIdx, TxIdx>> ready_chunks; |
1474 | | /** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on |
1475 | | * other chunks (not including dependencies within the chunk itself). */ |
1476 | 821k | std::vector<TxIdx> chunk_deps(m_set_info.size(), 0); |
1477 | | /** For every transaction, indexed by TxIdx, the number of unmet dependencies the |
1478 | | * transaction has. */ |
1479 | 821k | std::vector<TxIdx> tx_deps(m_tx_data.size(), 0); |
1480 | | /** A heap with all transactions within the current chunk that can be included, sorted by |
1481 | | * tx feerate (high to low), tx size (small to large), and fallback order. */ |
1482 | 821k | std::vector<TxIdx> ready_tx; |
1483 | | // Populate chunk_deps and tx_deps. |
1484 | 821k | unsigned num_deps{0}; |
1485 | 3.06M | for (TxIdx chl_idx : m_transaction_idxs) { |
1486 | 3.06M | const auto& chl_data = m_tx_data[chl_idx]; |
1487 | 3.06M | tx_deps[chl_idx] = chl_data.parents.Count(); |
1488 | 3.06M | num_deps += tx_deps[chl_idx]; |
1489 | 3.06M | auto chl_chunk_idx = chl_data.chunk_idx; |
1490 | 3.06M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; |
1491 | 3.06M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); |
1492 | 3.06M | } |
1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ |
1494 | 3.06M | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { |
1495 | 3.06M | auto& chunk = m_set_info[chunk_idx].transactions; |
1496 | 3.06M | auto it = chunk.begin(); |
1497 | 3.06M | DepGraphIndex ret = *it; |
1498 | 3.06M | ++it; |
1499 | 3.06M | while (it != chunk.end()) { |
1500 | 0 | if (fallback_order(*it, ret) > 0) ret = *it; |
1501 | 0 | ++it; |
1502 | 0 | } |
1503 | 3.06M | return ret; |
1504 | 3.06M | }; Unexecuted instantiation: std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(unsigned char)::operator()(unsigned char) const Unexecuted instantiation: txgraph.cpp:std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&)::'lambda'(unsigned char)::operator()(unsigned char) const txgraph.cpp:std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<(anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&)::'lambda'(unsigned char)::operator()(unsigned char) const Line | Count | Source | 1494 | 3.06M | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 3.06M | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 3.06M | auto it = chunk.begin(); | 1497 | 3.06M | DepGraphIndex ret = *it; | 1498 | 3.06M | ++it; | 1499 | 3.06M | while (it != chunk.end()) { | 1500 | 0 | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 0 | ++it; | 1502 | 0 | } | 1503 | 3.06M | return ret; | 1504 | 3.06M | }; |
|
1505 | | /** Comparison function for the transaction heap. Note that it is a max-heap, so |
1506 | | * tx_cmp_fn(a, b) == true means "a appears after b in the linearization". */ |
1507 | 821k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { |
1508 | | // Bail out for identical transactions. |
1509 | 0 | if (a == b) return false; |
1510 | | // First sort by increasing transaction feerate. |
1511 | 0 | auto& a_feerate = m_depgraph.FeeRate(a); |
1512 | 0 | auto& b_feerate = m_depgraph.FeeRate(b); |
1513 | 0 | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); |
1514 | 0 | if (feerate_cmp != 0) return feerate_cmp < 0; |
1515 | | // Then by decreasing transaction size. |
1516 | 0 | if (a_feerate.size != b_feerate.size) { |
1517 | 0 | return a_feerate.size > b_feerate.size; |
1518 | 0 | } |
1519 | | // Tie-break by decreasing fallback_order. |
1520 | 0 | auto fallback_cmp = fallback_order(a, b); |
1521 | 0 | if (fallback_cmp != 0) return fallback_cmp > 0; |
1522 | | // This should not be hit, because fallback_order defines a strong ordering. |
1523 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1524 | 0 | return a < b; |
1525 | 0 | }; Unexecuted instantiation: auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(std::compare_three_way const&, auto const&)::operator()<unsigned int, unsigned int>(std::compare_three_way const&, auto const&) const Unexecuted instantiation: txgraph.cpp:auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&)::'lambda'(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&, auto const&)::operator()<unsigned int, unsigned int>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&, auto const&) const Unexecuted instantiation: txgraph.cpp:auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<(anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&)::'lambda'((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&, auto const&)::operator()<unsigned int, unsigned int>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&, auto const&) const |
1526 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. |
1527 | | /** Comparison function for the chunk heap. Note that it is a max-heap, so |
1528 | | * chunk_cmp_fn(a, b) == true means "a appears after b in the linearization". */ |
1529 | 821k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { |
1530 | | // Bail out for identical chunks. |
1531 | 0 | if (a.first == b.first) return false; |
1532 | | // First sort by increasing chunk feerate. |
1533 | 0 | auto& chunk_feerate_a = m_set_info[a.first].feerate; |
1534 | 0 | auto& chunk_feerate_b = m_set_info[b.first].feerate; |
1535 | 0 | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); |
1536 | 0 | if (feerate_cmp != 0) return feerate_cmp < 0; |
1537 | | // Then by decreasing chunk size. |
1538 | 0 | if (chunk_feerate_a.size != chunk_feerate_b.size) { |
1539 | 0 | return chunk_feerate_a.size > chunk_feerate_b.size; |
1540 | 0 | } |
1541 | | // Tie-break by decreasing fallback_order. |
1542 | 0 | auto fallback_cmp = fallback_order(a.second, b.second); |
1543 | 0 | if (fallback_cmp != 0) return fallback_cmp > 0; |
1544 | | // This should not be hit, because fallback_order defines a strong ordering. |
1545 | 0 | Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(false); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1546 | 0 | return a.second < b.second; |
1547 | 0 | }; Unexecuted instantiation: auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda0'(std::compare_three_way const&, auto const&)::operator()<std::pair<unsigned char, unsigned int>, std::pair<unsigned char, unsigned int>>(std::compare_three_way const&, auto const&) const Unexecuted instantiation: txgraph.cpp:auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&)::'lambda0'(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&, auto const&)::operator()<std::pair<unsigned char, unsigned int>, std::pair<unsigned char, unsigned int>>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&, auto const&) const Unexecuted instantiation: txgraph.cpp:auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<(anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&)::'lambda0'((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&, auto const&)::operator()<std::pair<unsigned char, unsigned int>, std::pair<unsigned char, unsigned int>>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&, auto const&) const |
1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. |
1549 | 3.06M | for (SetIdx chunk_idx : m_chunk_idxs) { |
1550 | 3.06M | if (chunk_deps[chunk_idx] == 0) { |
1551 | 821k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); |
1552 | 821k | } |
1553 | 3.06M | } |
1554 | 821k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1555 | | // Pop chunks off the heap. |
1556 | 3.89M | while (!ready_chunks.empty()) { |
1557 | 3.06M | auto [chunk_idx, _rnd] = ready_chunks.front(); |
1558 | 3.06M | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1559 | 3.06M | ready_chunks.pop_back(); |
1560 | 3.06M | Assume(chunk_deps[chunk_idx] == 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_deps[chunk_idx] == 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_deps[chunk_idx] == 0); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1561 | 3.06M | const auto& chunk_txn = m_set_info[chunk_idx].transactions; |
1562 | | // Build heap of all includable transactions in chunk. |
1563 | 3.06M | Assume(ready_tx.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(ready_tx.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(ready_tx.empty()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1564 | 3.06M | for (TxIdx tx_idx : chunk_txn) { |
1565 | 3.06M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); |
1566 | 3.06M | } |
1567 | 3.06M | Assume(!ready_tx.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!ready_tx.empty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(!ready_tx.empty()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1568 | 3.06M | std::make_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); |
1569 | | // Pick transactions from the ready heap, append them to linearization, and decrement |
1570 | | // dependency counts. |
1571 | 6.13M | while (!ready_tx.empty()) { |
1572 | | // Pop an element from the tx_ready heap. |
1573 | 3.06M | auto tx_idx = ready_tx.front(); |
1574 | 3.06M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); |
1575 | 3.06M | ready_tx.pop_back(); |
1576 | | // Append to linearization. |
1577 | 3.06M | ret.push_back(tx_idx); |
1578 | | // Decrement dependency counts. |
1579 | 3.06M | auto& tx_data = m_tx_data[tx_idx]; |
1580 | 3.06M | for (TxIdx chl_idx : tx_data.children) { |
1581 | 2.24M | auto& chl_data = m_tx_data[chl_idx]; |
1582 | | // Decrement tx dependency count. |
1583 | 2.24M | Assume(tx_deps[chl_idx] > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(tx_deps[chl_idx] > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(tx_deps[chl_idx] > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1584 | 2.24M | if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) { |
1585 | | // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap. |
1586 | 0 | ready_tx.push_back(chl_idx); |
1587 | 0 | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); |
1588 | 0 | } |
1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. |
1590 | 2.24M | if (chl_data.chunk_idx != chunk_idx) { |
1591 | 2.24M | Assume(chunk_deps[chl_data.chunk_idx] > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_deps[chl_data.chunk_idx] > 0); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(chunk_deps[chl_data.chunk_idx] > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1592 | 2.24M | if (--chunk_deps[chl_data.chunk_idx] == 0) { |
1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. |
1594 | 2.24M | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); |
1595 | 2.24M | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1596 | 2.24M | } |
1597 | 2.24M | } |
1598 | 2.24M | } |
1599 | 3.06M | } |
1600 | 3.06M | } |
1601 | 821k | Assume(ret.size() == m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(ret.size() == m_set_info.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(ret.size() == m_set_info.size()); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1602 | 821k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); |
1603 | 821k | return ret; |
1604 | 821k | } Unexecuted instantiation: std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&) Unexecuted instantiation: txgraph.cpp:std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4>(txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&) txgraph.cpp:std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<(anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0>((anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&) Line | Count | Source | 1465 | 821k | { | 1466 | 821k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 821k | std::vector<DepGraphIndex> ret; | 1469 | 821k | ret.reserve(m_set_info.size()); | 1470 | | /** A heap with all chunks (by set index) that can currently be included, sorted by | 1471 | | * chunk feerate (high to low), chunk size (small to large), and by least maximum element | 1472 | | * according to the fallback order (which is the second pair element). */ | 1473 | 821k | std::vector<std::pair<SetIdx, TxIdx>> ready_chunks; | 1474 | | /** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on | 1475 | | * other chunks (not including dependencies within the chunk itself). */ | 1476 | 821k | std::vector<TxIdx> chunk_deps(m_set_info.size(), 0); | 1477 | | /** For every transaction, indexed by TxIdx, the number of unmet dependencies the | 1478 | | * transaction has. */ | 1479 | 821k | std::vector<TxIdx> tx_deps(m_tx_data.size(), 0); | 1480 | | /** A heap with all transactions within the current chunk that can be included, sorted by | 1481 | | * tx feerate (high to low), tx size (small to large), and fallback order. */ | 1482 | 821k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 821k | unsigned num_deps{0}; | 1485 | 3.06M | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 3.06M | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 3.06M | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 3.06M | num_deps += tx_deps[chl_idx]; | 1489 | 3.06M | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 3.06M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 3.06M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 3.06M | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 821k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 821k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 821k | auto it = chunk.begin(); | 1497 | 821k | DepGraphIndex ret = *it; | 1498 | 821k | ++it; | 1499 | 821k | while (it != chunk.end()) { | 1500 | 821k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 821k | ++it; | 1502 | 821k | } | 1503 | 821k | return ret; | 1504 | 821k | }; | 1505 | | /** Comparison function for the transaction heap. Note that it is a max-heap, so | 1506 | | * tx_cmp_fn(a, b) == true means "a appears after b in the linearization". */ | 1507 | 821k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 821k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 821k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 821k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 821k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 821k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 821k | if (a_feerate.size != b_feerate.size) { | 1517 | 821k | return a_feerate.size > b_feerate.size; | 1518 | 821k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 821k | auto fallback_cmp = fallback_order(a, b); | 1521 | 821k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 821k | Assume(false); | 1524 | 821k | return a < b; | 1525 | 821k | }; | 1526 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1527 | | /** Comparison function for the chunk heap. Note that it is a max-heap, so | 1528 | | * chunk_cmp_fn(a, b) == true means "a appears after b in the linearization". */ | 1529 | 821k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 821k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 821k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 821k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 821k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 821k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 821k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 821k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 821k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 821k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 821k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 821k | Assume(false); | 1546 | 821k | return a.second < b.second; | 1547 | 821k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 3.06M | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 3.06M | if (chunk_deps[chunk_idx] == 0) { | 1551 | 821k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 821k | } | 1553 | 3.06M | } | 1554 | 821k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 3.89M | while (!ready_chunks.empty()) { | 1557 | 3.06M | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 3.06M | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 3.06M | ready_chunks.pop_back(); | 1560 | 3.06M | Assume(chunk_deps[chunk_idx] == 0); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1561 | 3.06M | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 3.06M | Assume(ready_tx.empty()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1564 | 3.06M | for (TxIdx tx_idx : chunk_txn) { | 1565 | 3.06M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 3.06M | } | 1567 | 3.06M | Assume(!ready_tx.empty()); Line | Count | Source | 125 | 3.06M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1568 | 3.06M | std::make_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1569 | | // Pick transactions from the ready heap, append them to linearization, and decrement | 1570 | | // dependency counts. | 1571 | 6.13M | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 3.06M | auto tx_idx = ready_tx.front(); | 1574 | 3.06M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 3.06M | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 3.06M | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 3.06M | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 3.06M | for (TxIdx chl_idx : tx_data.children) { | 1581 | 2.24M | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 2.24M | Assume(tx_deps[chl_idx] > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1584 | 2.24M | if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) { | 1585 | | // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap. | 1586 | 0 | ready_tx.push_back(chl_idx); | 1587 | 0 | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 0 | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 2.24M | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 2.24M | Assume(chunk_deps[chl_data.chunk_idx] > 0); Line | Count | Source | 125 | 2.24M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1592 | 2.24M | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 2.24M | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 2.24M | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 2.24M | } | 1597 | 2.24M | } | 1598 | 2.24M | } | 1599 | 3.06M | } | 1600 | 3.06M | } | 1601 | 821k | Assume(ret.size() == m_set_info.size()); Line | Count | Source | 125 | 821k | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1602 | 821k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 821k | return ret; | 1604 | 821k | } |
|
1605 | | |
1606 | | /** Get the diagram for the current state, which must be topological. Test-only. |
1607 | | * |
1608 | | * The linearization produced by GetLinearization() is always at least as good (in the |
1609 | | * CompareChunks() sense) as this diagram, but may be better. |
1610 | | * |
1611 | | * After an OptimizeStep(), the diagram will always be at least as good as before. Once |
1612 | | * OptimizeStep() returns false, the diagram will be equivalent to that produced by |
1613 | | * GetLinearization(), and optimal. |
1614 | | * |
1615 | | * After a MinimizeStep(), the diagram cannot change anymore (in the CompareChunks() sense), |
1616 | | * but its number of segments can increase still. Once MinimizeStep() returns false, the number |
1617 | | * of chunks of the produced linearization will match the number of segments in the diagram. |
1618 | | */ |
1619 | | std::vector<FeeFrac> GetDiagram() const noexcept |
1620 | 0 | { |
1621 | 0 | std::vector<FeeFrac> ret; |
1622 | 0 | for (auto chunk_idx : m_chunk_idxs) { |
1623 | 0 | ret.push_back(m_set_info[chunk_idx].feerate); |
1624 | 0 | } |
1625 | 0 | std::sort(ret.begin(), ret.end(), std::greater{}); |
1626 | 0 | return ret; |
1627 | 0 | } |
1628 | | |
1629 | | /** Determine how much work was performed so far. */ |
1630 | 8.63M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); }Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Unexecuted instantiation: cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned long, 2u>, cluster_linearize::SFLDefaultCostModel>::GetCost() const cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 8.63M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
|
1631 | | |
1632 | | /** Verify internal consistency of the data structure. */ |
1633 | | void SanityCheck() const |
1634 | 0 | { |
1635 | | // |
1636 | | // Verify dependency parent/child information, and build list of (active) dependencies. |
1637 | | // |
1638 | 0 | std::vector<std::pair<TxIdx, TxIdx>> expected_dependencies; |
1639 | 0 | std::vector<std::pair<TxIdx, TxIdx>> all_dependencies; |
1640 | 0 | std::vector<std::pair<TxIdx, TxIdx>> active_dependencies; |
1641 | 0 | for (auto parent_idx : m_depgraph.Positions()) { |
1642 | 0 | for (auto child_idx : m_depgraph.GetReducedChildren(parent_idx)) { |
1643 | 0 | expected_dependencies.emplace_back(parent_idx, child_idx); |
1644 | 0 | } |
1645 | 0 | } |
1646 | 0 | for (auto tx_idx : m_transaction_idxs) { |
1647 | 0 | for (auto child_idx : m_tx_data[tx_idx].children) { |
1648 | 0 | all_dependencies.emplace_back(tx_idx, child_idx); |
1649 | 0 | if (m_tx_data[tx_idx].active_children[child_idx]) { |
1650 | 0 | active_dependencies.emplace_back(tx_idx, child_idx); |
1651 | 0 | } |
1652 | 0 | } |
1653 | 0 | } |
1654 | 0 | std::sort(expected_dependencies.begin(), expected_dependencies.end()); |
1655 | 0 | std::sort(all_dependencies.begin(), all_dependencies.end()); |
1656 | 0 | assert(expected_dependencies == all_dependencies); |
1657 | | |
1658 | | // |
1659 | | // Verify the chunks against the list of active dependencies |
1660 | | // |
1661 | 0 | SetType chunk_cover; |
1662 | 0 | for (auto chunk_idx : m_chunk_idxs) { |
1663 | 0 | const auto& chunk_info = m_set_info[chunk_idx]; |
1664 | | // Verify that transactions in the chunk point back to it. This guarantees |
1665 | | // that chunks are non-overlapping. |
1666 | 0 | for (auto tx_idx : chunk_info.transactions) { |
1667 | 0 | assert(m_tx_data[tx_idx].chunk_idx == chunk_idx); |
1668 | 0 | } |
1669 | 0 | assert(!chunk_cover.Overlaps(chunk_info.transactions)); |
1670 | 0 | chunk_cover |= chunk_info.transactions; |
1671 | | // Verify the chunk's transaction set: start from an arbitrary chunk transaction, |
1672 | | // and for every active dependency, if it contains the parent or child, add the |
1673 | | // other. It must have exactly N-1 active dependencies in it, guaranteeing it is |
1674 | | // acyclic. |
1675 | 0 | assert(chunk_info.transactions.Any()); |
1676 | 0 | SetType expected_chunk = SetType::Singleton(chunk_info.transactions.First()); |
1677 | 0 | while (true) { |
1678 | 0 | auto old = expected_chunk; |
1679 | 0 | size_t active_dep_count{0}; |
1680 | 0 | for (const auto& [par, chl] : active_dependencies) { |
1681 | 0 | if (expected_chunk[par] || expected_chunk[chl]) { |
1682 | 0 | expected_chunk.Set(par); |
1683 | 0 | expected_chunk.Set(chl); |
1684 | 0 | ++active_dep_count; |
1685 | 0 | } |
1686 | 0 | } |
1687 | 0 | if (old == expected_chunk) { |
1688 | 0 | assert(expected_chunk.Count() == active_dep_count + 1); |
1689 | 0 | break; |
1690 | 0 | } |
1691 | 0 | } |
1692 | 0 | assert(chunk_info.transactions == expected_chunk); |
1693 | | // Verify the chunk's feerate. |
1694 | 0 | assert(chunk_info.feerate == m_depgraph.FeeRate(chunk_info.transactions)); |
1695 | | // Verify the chunk's reachable transactions. |
1696 | 0 | assert(m_reachable[chunk_idx] == GetReachable(expected_chunk)); |
1697 | | // Verify that the chunk's reachable transactions don't include its own transactions. |
1698 | 0 | assert(!m_reachable[chunk_idx].first.Overlaps(chunk_info.transactions)); |
1699 | 0 | assert(!m_reachable[chunk_idx].second.Overlaps(chunk_info.transactions)); |
1700 | 0 | } |
1701 | | // Verify that together, the chunks cover all transactions. |
1702 | 0 | assert(chunk_cover == m_depgraph.Positions()); |
1703 | | |
1704 | | // |
1705 | | // Verify transaction data. |
1706 | | // |
1707 | 0 | assert(m_transaction_idxs == m_depgraph.Positions()); |
1708 | 0 | for (auto tx_idx : m_transaction_idxs) { |
1709 | 0 | const auto& tx_data = m_tx_data[tx_idx]; |
1710 | | // Verify it has a valid chunk index, and that chunk includes this transaction. |
1711 | 0 | assert(m_chunk_idxs[tx_data.chunk_idx]); |
1712 | 0 | assert(m_set_info[tx_data.chunk_idx].transactions[tx_idx]); |
1713 | | // Verify parents/children. |
1714 | 0 | assert(tx_data.parents == m_depgraph.GetReducedParents(tx_idx)); |
1715 | 0 | assert(tx_data.children == m_depgraph.GetReducedChildren(tx_idx)); |
1716 | | // Verify active_children is a subset of children. |
1717 | 0 | assert(tx_data.active_children.IsSubsetOf(tx_data.children)); |
1718 | | // Verify each active child's dep_top_idx points to a valid non-chunk set. |
1719 | 0 | for (auto child_idx : tx_data.active_children) { |
1720 | 0 | assert(tx_data.dep_top_idx[child_idx] < m_set_info.size()); |
1721 | 0 | assert(!m_chunk_idxs[tx_data.dep_top_idx[child_idx]]); |
1722 | 0 | } |
1723 | 0 | } |
1724 | | |
1725 | | // |
1726 | | // Verify active dependencies' top sets. |
1727 | | // |
1728 | 0 | for (const auto& [par_idx, chl_idx] : active_dependencies) { |
1729 | | // Verify the top set's transactions: it must contain the parent, and for every |
1730 | | // active dependency, except the chl_idx->par_idx dependency itself, if it contains the |
1731 | | // parent or child, it must contain both. It must have exactly N-1 active dependencies |
1732 | | // in it, guaranteeing it is acyclic. |
1733 | 0 | SetType expected_top = SetType::Singleton(par_idx); |
1734 | 0 | while (true) { |
1735 | 0 | auto old = expected_top; |
1736 | 0 | size_t active_dep_count{0}; |
1737 | 0 | for (const auto& [par2_idx, chl2_idx] : active_dependencies) { |
1738 | 0 | if (par_idx == par2_idx && chl_idx == chl2_idx) continue; |
1739 | 0 | if (expected_top[par2_idx] || expected_top[chl2_idx]) { |
1740 | 0 | expected_top.Set(par2_idx); |
1741 | 0 | expected_top.Set(chl2_idx); |
1742 | 0 | ++active_dep_count; |
1743 | 0 | } |
1744 | 0 | } |
1745 | 0 | if (old == expected_top) { |
1746 | 0 | assert(expected_top.Count() == active_dep_count + 1); |
1747 | 0 | break; |
1748 | 0 | } |
1749 | 0 | } |
1750 | 0 | assert(!expected_top[chl_idx]); |
1751 | 0 | auto& dep_top_info = m_set_info[m_tx_data[par_idx].dep_top_idx[chl_idx]]; |
1752 | 0 | assert(dep_top_info.transactions == expected_top); |
1753 | | // Verify the top set's feerate. |
1754 | 0 | assert(dep_top_info.feerate == m_depgraph.FeeRate(dep_top_info.transactions)); |
1755 | 0 | } |
1756 | | |
1757 | | // |
1758 | | // Verify m_suboptimal_chunks. |
1759 | | // |
1760 | 0 | SetType suboptimal_idxs; |
1761 | 0 | for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) { |
1762 | 0 | auto chunk_idx = m_suboptimal_chunks[i]; |
1763 | 0 | assert(!suboptimal_idxs[chunk_idx]); |
1764 | 0 | suboptimal_idxs.Set(chunk_idx); |
1765 | 0 | } |
1766 | 0 | assert(m_suboptimal_idxs == suboptimal_idxs); |
1767 | | |
1768 | | // |
1769 | | // Verify m_nonminimal_chunks. |
1770 | | // |
1771 | 0 | SetType nonminimal_idxs; |
1772 | 0 | for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) { |
1773 | 0 | auto [chunk_idx, pivot, flags] = m_nonminimal_chunks[i]; |
1774 | 0 | assert(m_tx_data[pivot].chunk_idx == chunk_idx); |
1775 | 0 | assert(!nonminimal_idxs[chunk_idx]); |
1776 | 0 | nonminimal_idxs.Set(chunk_idx); |
1777 | 0 | } |
1778 | 0 | assert(nonminimal_idxs.IsSubsetOf(m_chunk_idxs)); |
1779 | 0 | } |
1780 | | }; |
1781 | | |
1782 | | /** Find or improve a linearization for a cluster. |
1783 | | * |
1784 | | * @param[in] depgraph Dependency graph of the cluster to be linearized. |
1785 | | * @param[in] max_cost Upper bound on the amount of work that will be done. |
1786 | | * @param[in] rng_seed A random number seed to control search order. This prevents peers |
1787 | | * from predicting exactly which clusters would be hard for us to |
1788 | | * linearize. |
1789 | | * @param[in] fallback_order A comparator to order transactions, used to sort equal-feerate |
1790 | | * chunks and transactions. See SpanningForestState::GetLinearization |
1791 | | * for details. |
1792 | | * @param[in] old_linearization An existing linearization for the cluster, or empty. |
1793 | | * @param[in] is_topological (Only relevant if old_linearization is not empty) Whether |
1794 | | * old_linearization is topologically valid. |
1795 | | * @return A tuple of: |
1796 | | * - The resulting linearization. It is guaranteed to be at least as |
1797 | | * good (in the feerate diagram sense) as old_linearization. |
1798 | | * - A boolean indicating whether the result is guaranteed to be |
1799 | | * optimal with minimal chunks. |
1800 | | * - How many optimization steps were actually performed. |
1801 | | */ |
1802 | | template<typename SetType> |
1803 | | std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize( |
1804 | | const DepGraph<SetType>& depgraph, |
1805 | | uint64_t max_cost, |
1806 | | uint64_t rng_seed, |
1807 | | const StrongComparator<DepGraphIndex> auto& fallback_order, |
1808 | | std::span<const DepGraphIndex> old_linearization = {}, |
1809 | | bool is_topological = true) noexcept |
1810 | 821k | { |
1811 | | /** Initialize a spanning forest data structure for this cluster. */ |
1812 | 821k | SpanningForestState forest(depgraph, rng_seed); |
1813 | 821k | if (!old_linearization.empty()) { |
1814 | 821k | forest.LoadLinearization(old_linearization); |
1815 | 821k | if (!is_topological) forest.MakeTopological()784k ; |
1816 | 821k | } else { |
1817 | 0 | forest.MakeTopological(); |
1818 | 0 | } |
1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result |
1820 | | // is found. |
1821 | 821k | if (forest.GetCost() < max_cost) { |
1822 | 821k | forest.StartOptimizing(); |
1823 | 821k | do { |
1824 | 821k | if (!forest.OptimizeStep()) break; |
1825 | 821k | } while (forest.GetCost() < max_cost0 ); |
1826 | 821k | } |
1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are |
1828 | | // minimal. |
1829 | 0 | bool optimal = false; |
1830 | 821k | if (forest.GetCost() < max_cost) { |
1831 | 821k | forest.StartMinimizing(); |
1832 | 6.99M | do { |
1833 | 6.99M | if (!forest.MinimizeStep()) { |
1834 | 821k | optimal = true; |
1835 | 821k | break; |
1836 | 821k | } |
1837 | 6.99M | } while (forest.GetCost() < max_cost6.16M ); |
1838 | 821k | } |
1839 | 0 | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; |
1840 | 821k | } Unexecuted instantiation: std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::IntBitSet<unsigned int>, std::compare_three_way>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, unsigned long, unsigned long, std::compare_three_way const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Unexecuted instantiation: txgraph.cpp:std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::MultiIntBitSet<unsigned long, 2u>, txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, unsigned long, unsigned long, txgraph_fuzz_target(std::span<unsigned char const, 18446744073709551615ul>)::$_4 const&, std::span<unsigned int const, 18446744073709551615ul>, bool) txgraph.cpp:std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::IntBitSet<unsigned long>, (anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, unsigned long, unsigned long, (anonymous namespace)::GenericClusterImpl::Relinearize((anonymous namespace)::TxGraphImpl&, int, unsigned long)::$_0 const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Line | Count | Source | 1810 | 821k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 821k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 821k | if (!old_linearization.empty()) { | 1814 | 821k | forest.LoadLinearization(old_linearization); | 1815 | 821k | if (!is_topological) forest.MakeTopological()784k ; | 1816 | 821k | } else { | 1817 | 0 | forest.MakeTopological(); | 1818 | 0 | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 821k | if (forest.GetCost() < max_cost) { | 1822 | 821k | forest.StartOptimizing(); | 1823 | 821k | do { | 1824 | 821k | if (!forest.OptimizeStep()) break; | 1825 | 821k | } while (forest.GetCost() < max_cost0 ); | 1826 | 821k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 0 | bool optimal = false; | 1830 | 821k | if (forest.GetCost() < max_cost) { | 1831 | 821k | forest.StartMinimizing(); | 1832 | 6.99M | do { | 1833 | 6.99M | if (!forest.MinimizeStep()) { | 1834 | 821k | optimal = true; | 1835 | 821k | break; | 1836 | 821k | } | 1837 | 6.99M | } while (forest.GetCost() < max_cost6.16M ); | 1838 | 821k | } | 1839 | 0 | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 821k | } |
|
1841 | | |
1842 | | /** Improve a given linearization. |
1843 | | * |
1844 | | * @param[in] depgraph Dependency graph of the cluster being linearized. |
1845 | | * @param[in,out] linearization On input, an existing linearization for depgraph. On output, a |
1846 | | * potentially better linearization for the same graph. |
1847 | | * |
1848 | | * Postlinearization guarantees: |
1849 | | * - The resulting chunks are connected. |
1850 | | * - If the input has a tree shape (either all transactions have at most one child, or all |
1851 | | * transactions have at most one parent), the result is optimal. |
1852 | | * - Given a linearization L1 and a leaf transaction T in it. Let L2 be L1 with T moved to the end, |
1853 | | * optionally with its fee increased. Let L3 be the postlinearization of L2. L3 will be at least |
1854 | | * as good as L1. This means that replacing transactions with same-size higher-fee transactions |
1855 | | * will not worsen linearizations through a "drop conflicts, append new transactions, |
1856 | | * postlinearize" process. |
1857 | | */ |
1858 | | template<typename SetType> |
1859 | | void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization) |
1860 | 821k | { |
1861 | | // This algorithm performs a number of passes (currently 2); the even ones operate from back to |
1862 | | // front, the odd ones from front to back. Each results in an equal-or-better linearization |
1863 | | // than the one started from. |
1864 | | // - One pass in either direction guarantees that the resulting chunks are connected. |
1865 | | // - Each direction corresponds to one shape of tree being linearized optimally (forward passes |
1866 | | // guarantee this for graphs where each transaction has at most one child; backward passes |
1867 | | // guarantee this for graphs where each transaction has at most one parent). |
1868 | | // - Starting with a backward pass guarantees the moved-tree property. |
1869 | | // |
1870 | | // During an odd (forward) pass, the high-level operation is: |
1871 | | // - Start with an empty list of groups L=[]. |
1872 | | // - For every transaction i in the old linearization, from front to back: |
1873 | | // - Append a new group C=[i], containing just i, to the back of L. |
1874 | | // - While L has at least one group before C, and the group immediately before C has feerate |
1875 | | // lower than C: |
1876 | | // - If C depends on P: |
1877 | | // - Merge P into C, making C the concatenation of P+C, continuing with the combined C. |
1878 | | // - Otherwise: |
1879 | | // - Swap P with C, continuing with the now-moved C. |
1880 | | // - The output linearization is the concatenation of the groups in L. |
1881 | | // |
1882 | | // During even (backward) passes, i iterates from the back to the front of the existing |
1883 | | // linearization, and new groups are prepended instead of appended to the list L. To enable |
1884 | | // more code reuse, both passes append groups, but during even passes the meanings of |
1885 | | // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed |
1886 | | // on output. |
1887 | | // |
1888 | | // In the implementation below, the groups are represented by singly-linked lists (pointing |
1889 | | // from the back to the front), which are themselves organized in a singly-linked circular |
1890 | | // list (each group pointing to its predecessor, with a special sentinel group at the front |
1891 | | // that points back to the last group). |
1892 | | // |
1893 | | // Information about transaction t is stored in entries[t + 1], while the sentinel is in |
1894 | | // entries[0]. |
1895 | | |
1896 | | /** Index of the sentinel in the entries array below. */ |
1897 | 821k | static constexpr DepGraphIndex SENTINEL{0}; |
1898 | | /** Indicator that a group has no previous transaction. */ |
1899 | 821k | static constexpr DepGraphIndex NO_PREV_TX{0}; |
1900 | | |
1901 | | |
1902 | | /** Data structure per transaction entry. */ |
1903 | 821k | struct TxEntry |
1904 | 821k | { |
1905 | | /** The index of the previous transaction in this group; NO_PREV_TX if this is the first |
1906 | | * entry of a group. */ |
1907 | 821k | DepGraphIndex prev_tx; |
1908 | | |
1909 | | // The fields below are only used for transactions that are the last one in a group |
1910 | | // (referred to as tail transactions below). |
1911 | | |
1912 | | /** Index of the first transaction in this group, possibly itself. */ |
1913 | 821k | DepGraphIndex first_tx; |
1914 | | /** Index of the last transaction in the previous group. The first group (the sentinel) |
1915 | | * points back to the last group here, making it a singly-linked circular list. */ |
1916 | 821k | DepGraphIndex prev_group; |
1917 | | /** All transactions in the group. Empty for the sentinel. */ |
1918 | 821k | SetType group; |
1919 | | /** All dependencies of the group (descendants in even passes; ancestors in odd ones). */ |
1920 | 821k | SetType deps; |
1921 | | /** The combined fee/size of transactions in the group. Fee is negated in even passes. */ |
1922 | 821k | FeeFrac feerate; |
1923 | 821k | }; |
1924 | | |
1925 | | // As an example, consider the state corresponding to the linearization [1,0,3,2], with |
1926 | | // groups [1,0,3] and [2], in an odd pass. The linked lists would be: |
1927 | | // |
1928 | | // +-----+ |
1929 | | // 0<-P-- | 0 S | ---\ Legend: |
1930 | | // +-----+ | |
1931 | | // ^ | - digit in box: entries index |
1932 | | // /--------------F---------+ G | (note: one more than tx value) |
1933 | | // v \ | | - S: sentinel group |
1934 | | // +-----+ +-----+ +-----+ | (empty feerate) |
1935 | | // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains |
1936 | | // +-----+ +-----+ +-----+ | fields beyond prev_tv. |
1937 | | // ^ | - P: prev_tx reference |
1938 | | // G G - F: first_tx reference |
1939 | | // | | - G: prev_group reference |
1940 | | // +-----+ | |
1941 | | // 0<-P-- | 3 T | <--/ |
1942 | | // +-----+ |
1943 | | // ^ | |
1944 | | // \-F-/ |
1945 | | // |
1946 | | // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with |
1947 | | // groups [2] and [3,0,1]. |
1948 | | |
1949 | 821k | std::vector<TxEntry> entries(depgraph.PositionRange() + 1); |
1950 | | |
1951 | | // Perform two passes over the linearization. |
1952 | 2.46M | for (int pass = 0; pass < 2; ++pass1.64M ) { |
1953 | 1.64M | int rev = !(pass & 1); |
1954 | | // Construct a sentinel group, identifying the start of the list. |
1955 | 1.64M | entries[SENTINEL].prev_group = SENTINEL; |
1956 | 1.64M | Assume(entries[SENTINEL].feerate.IsEmpty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(entries[SENTINEL].feerate.IsEmpty()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(entries[SENTINEL].feerate.IsEmpty()); Line | Count | Source | 125 | 1.64M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1957 | | |
1958 | | // Iterate over all elements in the existing linearization. |
1959 | 7.78M | for (DepGraphIndex i = 0; i < linearization.size(); ++i6.13M ) { |
1960 | | // Even passes are from back to front; odd passes from front to back. |
1961 | 6.13M | DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i3.06M : i3.06M ]; |
1962 | | // Construct a new group containing just idx. In even passes, the meaning of |
1963 | | // parent/child and high/low feerate are swapped. |
1964 | 6.13M | DepGraphIndex cur_group = idx + 1; |
1965 | 6.13M | entries[cur_group].group = SetType::Singleton(idx); |
1966 | 6.13M | entries[cur_group].deps = rev ? depgraph.Descendants(idx)3.06M : depgraph.Ancestors(idx)3.06M ; |
1967 | 6.13M | entries[cur_group].feerate = depgraph.FeeRate(idx); |
1968 | 6.13M | if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee3.06M ; |
1969 | 6.13M | entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group. |
1970 | 6.13M | entries[cur_group].first_tx = cur_group; // Transaction itself is first of group. |
1971 | | // Insert the new group at the back of the groups linked list. |
1972 | 6.13M | entries[cur_group].prev_group = entries[SENTINEL].prev_group; |
1973 | 6.13M | entries[SENTINEL].prev_group = cur_group; |
1974 | | |
1975 | | // Start merge/swap cycle. |
1976 | 6.13M | DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel. |
1977 | 6.13M | DepGraphIndex prev_group = entries[cur_group].prev_group; |
1978 | | // Continue as long as the current group has higher feerate than the previous one. |
1979 | 6.13M | while (entries[cur_group].feerate >> entries[prev_group].feerate) { |
1980 | | // prev_group/cur_group/next_group refer to (the last transactions of) 3 |
1981 | | // consecutive entries in groups list. |
1982 | 0 | Assume(cur_group == entries[next_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(cur_group == entries[next_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(cur_group == entries[next_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1983 | 0 | Assume(prev_group == entries[cur_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(prev_group == entries[cur_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(prev_group == entries[cur_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1984 | | // The sentinel has empty feerate, which is neither higher or lower than other |
1985 | | // feerates. Thus, the while loop we are in here guarantees that cur_group and |
1986 | | // prev_group are not the sentinel. |
1987 | 0 | Assume(cur_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(cur_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(cur_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1988 | 0 | Assume(prev_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(prev_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(prev_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
1989 | 0 | if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) { |
1990 | | // There is a dependency between cur_group and prev_group; merge prev_group |
1991 | | // into cur_group. The group/deps/feerate fields of prev_group remain unchanged |
1992 | | // but become unused. |
1993 | 0 | entries[cur_group].group |= entries[prev_group].group; |
1994 | 0 | entries[cur_group].deps |= entries[prev_group].deps; |
1995 | 0 | entries[cur_group].feerate += entries[prev_group].feerate; |
1996 | | // Make the first of the current group point to the tail of the previous group. |
1997 | 0 | entries[entries[cur_group].first_tx].prev_tx = prev_group; |
1998 | | // The first of the previous group becomes the first of the newly-merged group. |
1999 | 0 | entries[cur_group].first_tx = entries[prev_group].first_tx; |
2000 | | // The previous group becomes whatever group was before the former one. |
2001 | 0 | prev_group = entries[prev_group].prev_group; |
2002 | 0 | entries[cur_group].prev_group = prev_group; |
2003 | 0 | } else { |
2004 | | // There is no dependency between cur_group and prev_group; swap them. |
2005 | 0 | DepGraphIndex preprev_group = entries[prev_group].prev_group; |
2006 | | // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new |
2007 | | // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order. |
2008 | 0 | entries[next_group].prev_group = prev_group; |
2009 | 0 | entries[prev_group].prev_group = cur_group; |
2010 | 0 | entries[cur_group].prev_group = preprev_group; |
2011 | | // The current group remains the same, but the groups before/after it have |
2012 | | // changed. |
2013 | 0 | next_group = prev_group; |
2014 | 0 | prev_group = preprev_group; |
2015 | 0 | } |
2016 | 0 | } |
2017 | 6.13M | } |
2018 | | |
2019 | | // Convert the entries back to linearization (overwriting the existing one). |
2020 | 1.64M | DepGraphIndex cur_group = entries[0].prev_group; |
2021 | 1.64M | DepGraphIndex done = 0; |
2022 | 7.78M | while (cur_group != SENTINEL) { |
2023 | 6.13M | DepGraphIndex cur_tx = cur_group; |
2024 | | // Traverse the transactions of cur_group (from back to front), and write them in the |
2025 | | // same order during odd passes, and reversed (front to back) in even passes. |
2026 | 6.13M | if (rev) { |
2027 | 3.06M | do { |
2028 | 3.06M | *(linearization.begin() + (done++)) = cur_tx - 1; |
2029 | 3.06M | cur_tx = entries[cur_tx].prev_tx; |
2030 | 3.06M | } while (cur_tx != NO_PREV_TX); |
2031 | 3.06M | } else { |
2032 | 3.06M | do { |
2033 | 3.06M | *(linearization.end() - (++done)) = cur_tx - 1; |
2034 | 3.06M | cur_tx = entries[cur_tx].prev_tx; |
2035 | 3.06M | } while (cur_tx != NO_PREV_TX); |
2036 | 3.06M | } |
2037 | 6.13M | cur_group = entries[cur_group].prev_group; |
2038 | 6.13M | } |
2039 | 1.64M | Assume(done == linearization.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(done == linearization.size()); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| Assume(done == linearization.size()); Line | Count | Source | 125 | 1.64M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
|
2040 | 1.64M | } |
2041 | 821k | } Unexecuted instantiation: void cluster_linearize::PostLinearize<bitset_detail::IntBitSet<unsigned int>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, std::span<unsigned int, 18446744073709551615ul>) Unexecuted instantiation: void cluster_linearize::PostLinearize<bitset_detail::MultiIntBitSet<unsigned long, 2u>>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned long, 2u>> const&, std::span<unsigned int, 18446744073709551615ul>) void cluster_linearize::PostLinearize<bitset_detail::IntBitSet<unsigned long>>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, std::span<unsigned int, 18446744073709551615ul>) Line | Count | Source | 1860 | 821k | { | 1861 | | // This algorithm performs a number of passes (currently 2); the even ones operate from back to | 1862 | | // front, the odd ones from front to back. Each results in an equal-or-better linearization | 1863 | | // than the one started from. | 1864 | | // - One pass in either direction guarantees that the resulting chunks are connected. | 1865 | | // - Each direction corresponds to one shape of tree being linearized optimally (forward passes | 1866 | | // guarantee this for graphs where each transaction has at most one child; backward passes | 1867 | | // guarantee this for graphs where each transaction has at most one parent). | 1868 | | // - Starting with a backward pass guarantees the moved-tree property. | 1869 | | // | 1870 | | // During an odd (forward) pass, the high-level operation is: | 1871 | | // - Start with an empty list of groups L=[]. | 1872 | | // - For every transaction i in the old linearization, from front to back: | 1873 | | // - Append a new group C=[i], containing just i, to the back of L. | 1874 | | // - While L has at least one group before C, and the group immediately before C has feerate | 1875 | | // lower than C: | 1876 | | // - If C depends on P: | 1877 | | // - Merge P into C, making C the concatenation of P+C, continuing with the combined C. | 1878 | | // - Otherwise: | 1879 | | // - Swap P with C, continuing with the now-moved C. | 1880 | | // - The output linearization is the concatenation of the groups in L. | 1881 | | // | 1882 | | // During even (backward) passes, i iterates from the back to the front of the existing | 1883 | | // linearization, and new groups are prepended instead of appended to the list L. To enable | 1884 | | // more code reuse, both passes append groups, but during even passes the meanings of | 1885 | | // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed | 1886 | | // on output. | 1887 | | // | 1888 | | // In the implementation below, the groups are represented by singly-linked lists (pointing | 1889 | | // from the back to the front), which are themselves organized in a singly-linked circular | 1890 | | // list (each group pointing to its predecessor, with a special sentinel group at the front | 1891 | | // that points back to the last group). | 1892 | | // | 1893 | | // Information about transaction t is stored in entries[t + 1], while the sentinel is in | 1894 | | // entries[0]. | 1895 | | | 1896 | | /** Index of the sentinel in the entries array below. */ | 1897 | 821k | static constexpr DepGraphIndex SENTINEL{0}; | 1898 | | /** Indicator that a group has no previous transaction. */ | 1899 | 821k | static constexpr DepGraphIndex NO_PREV_TX{0}; | 1900 | | | 1901 | | | 1902 | | /** Data structure per transaction entry. */ | 1903 | 821k | struct TxEntry | 1904 | 821k | { | 1905 | | /** The index of the previous transaction in this group; NO_PREV_TX if this is the first | 1906 | | * entry of a group. */ | 1907 | 821k | DepGraphIndex prev_tx; | 1908 | | | 1909 | | // The fields below are only used for transactions that are the last one in a group | 1910 | | // (referred to as tail transactions below). | 1911 | | | 1912 | | /** Index of the first transaction in this group, possibly itself. */ | 1913 | 821k | DepGraphIndex first_tx; | 1914 | | /** Index of the last transaction in the previous group. The first group (the sentinel) | 1915 | | * points back to the last group here, making it a singly-linked circular list. */ | 1916 | 821k | DepGraphIndex prev_group; | 1917 | | /** All transactions in the group. Empty for the sentinel. */ | 1918 | 821k | SetType group; | 1919 | | /** All dependencies of the group (descendants in even passes; ancestors in odd ones). */ | 1920 | 821k | SetType deps; | 1921 | | /** The combined fee/size of transactions in the group. Fee is negated in even passes. */ | 1922 | 821k | FeeFrac feerate; | 1923 | 821k | }; | 1924 | | | 1925 | | // As an example, consider the state corresponding to the linearization [1,0,3,2], with | 1926 | | // groups [1,0,3] and [2], in an odd pass. The linked lists would be: | 1927 | | // | 1928 | | // +-----+ | 1929 | | // 0<-P-- | 0 S | ---\ Legend: | 1930 | | // +-----+ | | 1931 | | // ^ | - digit in box: entries index | 1932 | | // /--------------F---------+ G | (note: one more than tx value) | 1933 | | // v \ | | - S: sentinel group | 1934 | | // +-----+ +-----+ +-----+ | (empty feerate) | 1935 | | // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains | 1936 | | // +-----+ +-----+ +-----+ | fields beyond prev_tv. | 1937 | | // ^ | - P: prev_tx reference | 1938 | | // G G - F: first_tx reference | 1939 | | // | | - G: prev_group reference | 1940 | | // +-----+ | | 1941 | | // 0<-P-- | 3 T | <--/ | 1942 | | // +-----+ | 1943 | | // ^ | | 1944 | | // \-F-/ | 1945 | | // | 1946 | | // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with | 1947 | | // groups [2] and [3,0,1]. | 1948 | | | 1949 | 821k | std::vector<TxEntry> entries(depgraph.PositionRange() + 1); | 1950 | | | 1951 | | // Perform two passes over the linearization. | 1952 | 2.46M | for (int pass = 0; pass < 2; ++pass1.64M ) { | 1953 | 1.64M | int rev = !(pass & 1); | 1954 | | // Construct a sentinel group, identifying the start of the list. | 1955 | 1.64M | entries[SENTINEL].prev_group = SENTINEL; | 1956 | 1.64M | Assume(entries[SENTINEL].feerate.IsEmpty()); Line | Count | Source | 125 | 1.64M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1957 | | | 1958 | | // Iterate over all elements in the existing linearization. | 1959 | 7.78M | for (DepGraphIndex i = 0; i < linearization.size(); ++i6.13M ) { | 1960 | | // Even passes are from back to front; odd passes from front to back. | 1961 | 6.13M | DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i3.06M : i3.06M ]; | 1962 | | // Construct a new group containing just idx. In even passes, the meaning of | 1963 | | // parent/child and high/low feerate are swapped. | 1964 | 6.13M | DepGraphIndex cur_group = idx + 1; | 1965 | 6.13M | entries[cur_group].group = SetType::Singleton(idx); | 1966 | 6.13M | entries[cur_group].deps = rev ? depgraph.Descendants(idx)3.06M : depgraph.Ancestors(idx)3.06M ; | 1967 | 6.13M | entries[cur_group].feerate = depgraph.FeeRate(idx); | 1968 | 6.13M | if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee3.06M ; | 1969 | 6.13M | entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group. | 1970 | 6.13M | entries[cur_group].first_tx = cur_group; // Transaction itself is first of group. | 1971 | | // Insert the new group at the back of the groups linked list. | 1972 | 6.13M | entries[cur_group].prev_group = entries[SENTINEL].prev_group; | 1973 | 6.13M | entries[SENTINEL].prev_group = cur_group; | 1974 | | | 1975 | | // Start merge/swap cycle. | 1976 | 6.13M | DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel. | 1977 | 6.13M | DepGraphIndex prev_group = entries[cur_group].prev_group; | 1978 | | // Continue as long as the current group has higher feerate than the previous one. | 1979 | 6.13M | while (entries[cur_group].feerate >> entries[prev_group].feerate) { | 1980 | | // prev_group/cur_group/next_group refer to (the last transactions of) 3 | 1981 | | // consecutive entries in groups list. | 1982 | 0 | Assume(cur_group == entries[next_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1983 | 0 | Assume(prev_group == entries[cur_group].prev_group); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1984 | | // The sentinel has empty feerate, which is neither higher or lower than other | 1985 | | // feerates. Thus, the while loop we are in here guarantees that cur_group and | 1986 | | // prev_group are not the sentinel. | 1987 | 0 | Assume(cur_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1988 | 0 | Assume(prev_group != SENTINEL); Line | Count | Source | 125 | 0 | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 1989 | 0 | if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) { | 1990 | | // There is a dependency between cur_group and prev_group; merge prev_group | 1991 | | // into cur_group. The group/deps/feerate fields of prev_group remain unchanged | 1992 | | // but become unused. | 1993 | 0 | entries[cur_group].group |= entries[prev_group].group; | 1994 | 0 | entries[cur_group].deps |= entries[prev_group].deps; | 1995 | 0 | entries[cur_group].feerate += entries[prev_group].feerate; | 1996 | | // Make the first of the current group point to the tail of the previous group. | 1997 | 0 | entries[entries[cur_group].first_tx].prev_tx = prev_group; | 1998 | | // The first of the previous group becomes the first of the newly-merged group. | 1999 | 0 | entries[cur_group].first_tx = entries[prev_group].first_tx; | 2000 | | // The previous group becomes whatever group was before the former one. | 2001 | 0 | prev_group = entries[prev_group].prev_group; | 2002 | 0 | entries[cur_group].prev_group = prev_group; | 2003 | 0 | } else { | 2004 | | // There is no dependency between cur_group and prev_group; swap them. | 2005 | 0 | DepGraphIndex preprev_group = entries[prev_group].prev_group; | 2006 | | // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new | 2007 | | // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order. | 2008 | 0 | entries[next_group].prev_group = prev_group; | 2009 | 0 | entries[prev_group].prev_group = cur_group; | 2010 | 0 | entries[cur_group].prev_group = preprev_group; | 2011 | | // The current group remains the same, but the groups before/after it have | 2012 | | // changed. | 2013 | 0 | next_group = prev_group; | 2014 | 0 | prev_group = preprev_group; | 2015 | 0 | } | 2016 | 0 | } | 2017 | 6.13M | } | 2018 | | | 2019 | | // Convert the entries back to linearization (overwriting the existing one). | 2020 | 1.64M | DepGraphIndex cur_group = entries[0].prev_group; | 2021 | 1.64M | DepGraphIndex done = 0; | 2022 | 7.78M | while (cur_group != SENTINEL) { | 2023 | 6.13M | DepGraphIndex cur_tx = cur_group; | 2024 | | // Traverse the transactions of cur_group (from back to front), and write them in the | 2025 | | // same order during odd passes, and reversed (front to back) in even passes. | 2026 | 6.13M | if (rev) { | 2027 | 3.06M | do { | 2028 | 3.06M | *(linearization.begin() + (done++)) = cur_tx - 1; | 2029 | 3.06M | cur_tx = entries[cur_tx].prev_tx; | 2030 | 3.06M | } while (cur_tx != NO_PREV_TX); | 2031 | 3.06M | } else { | 2032 | 3.06M | do { | 2033 | 3.06M | *(linearization.end() - (++done)) = cur_tx - 1; | 2034 | 3.06M | cur_tx = entries[cur_tx].prev_tx; | 2035 | 3.06M | } while (cur_tx != NO_PREV_TX); | 2036 | 3.06M | } | 2037 | 6.13M | cur_group = entries[cur_group].prev_group; | 2038 | 6.13M | } | 2039 | 1.64M | Assume(done == linearization.size()); Line | Count | Source | 125 | 1.64M | #define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val) |
| 2040 | 1.64M | } | 2041 | 821k | } |
|
2042 | | |
2043 | | } // namespace cluster_linearize |
2044 | | |
2045 | | #endif // BITCOIN_CLUSTER_LINEARIZE_H |