/tmp/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 | 1.00M | friend bool operator==(const Entry&, const Entry&) noexcept = default; cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Entry const&, cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Entry const&) Line | Count | Source | 43 | 277k | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Entry const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Entry const&) Line | Count | Source | 43 | 277k | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Entry const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Entry const&) Line | Count | Source | 43 | 277k | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Entry const&, cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Entry const&) Line | Count | Source | 43 | 85.4k | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Entry const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Entry const&) Line | Count | Source | 43 | 84.2k | friend bool operator==(const Entry&, const Entry&) noexcept = default; |
|
44 | | |
45 | | /** Construct an empty entry. */ |
46 | 75.5k | Entry() noexcept = default; cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Entry::Entry() Line | Count | Source | 46 | 20.9k | Entry() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Entry::Entry() Line | Count | Source | 46 | 20.9k | Entry() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Entry::Entry() Line | Count | Source | 46 | 20.9k | Entry() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Entry::Entry() Line | Count | Source | 46 | 6.48k | Entry() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Entry::Entry() Line | Count | Source | 46 | 6.39k | Entry() noexcept = default; |
|
47 | | /** Construct an entry with a given feerate, ancestor set, descendant set. */ |
48 | 82.1k | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}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 | 27.8k | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {} |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Entry::Entry(FeeFrac const&, bitset_detail::MultiIntBitSet<unsigned int, 2u> const&, bitset_detail::MultiIntBitSet<unsigned int, 2u> const&) Line | Count | Source | 48 | 20.8k | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {} |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Entry::Entry(FeeFrac const&, bitset_detail::MultiIntBitSet<unsigned char, 8u> const&, bitset_detail::MultiIntBitSet<unsigned char, 8u> const&) Line | Count | Source | 48 | 20.8k | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {} |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Entry::Entry(FeeFrac const&, bitset_detail::IntBitSet<unsigned int> const&, bitset_detail::IntBitSet<unsigned int> const&) Line | Count | Source | 48 | 6.40k | Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {} |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Entry::Entry(FeeFrac const&, bitset_detail::MultiIntBitSet<unsigned char, 4u> const&, bitset_detail::MultiIntBitSet<unsigned char, 4u> const&) Line | Count | Source | 48 | 6.31k | 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 | 1.88k | { |
61 | 1.88k | if (a.m_used != b.m_used) return false; |
62 | | // Only compare the used positions within the entries vector. |
63 | 50.0k | for (auto idx : a.m_used) { |
64 | 50.0k | if (a.entries[idx] != b.entries[idx]) return false; |
65 | 50.0k | } |
66 | 1.88k | return true; |
67 | 1.88k | } cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&) Line | Count | Source | 60 | 454 | { | 61 | 454 | if (a.m_used != b.m_used) return false; | 62 | | // Only compare the used positions within the entries vector. | 63 | 13.8k | for (auto idx : a.m_used) { | 64 | 13.8k | if (a.entries[idx] != b.entries[idx]) return false; | 65 | 13.8k | } | 66 | 454 | return true; | 67 | 454 | } |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&) Line | Count | Source | 60 | 454 | { | 61 | 454 | if (a.m_used != b.m_used) return false; | 62 | | // Only compare the used positions within the entries vector. | 63 | 13.8k | for (auto idx : a.m_used) { | 64 | 13.8k | if (a.entries[idx] != b.entries[idx]) return false; | 65 | 13.8k | } | 66 | 454 | return true; | 67 | 454 | } |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&) Line | Count | Source | 60 | 454 | { | 61 | 454 | if (a.m_used != b.m_used) return false; | 62 | | // Only compare the used positions within the entries vector. | 63 | 13.8k | for (auto idx : a.m_used) { | 64 | 13.8k | if (a.entries[idx] != b.entries[idx]) return false; | 65 | 13.8k | } | 66 | 454 | return true; | 67 | 454 | } |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&) Line | Count | Source | 60 | 271 | { | 61 | 271 | if (a.m_used != b.m_used) return false; | 62 | | // Only compare the used positions within the entries vector. | 63 | 4.27k | for (auto idx : a.m_used) { | 64 | 4.27k | if (a.entries[idx] != b.entries[idx]) return false; | 65 | 4.27k | } | 66 | 271 | return true; | 67 | 271 | } |
cluster_linearize::operator==(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&, cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&) Line | Count | Source | 60 | 250 | { | 61 | 250 | if (a.m_used != b.m_used) return false; | 62 | | // Only compare the used positions within the entries vector. | 63 | 4.21k | for (auto idx : a.m_used) { | 64 | 4.21k | if (a.entries[idx] != b.entries[idx]) return false; | 65 | 4.21k | } | 66 | 250 | return true; | 67 | 250 | } |
|
68 | | |
69 | | // Default constructors. |
70 | 8.61k | DepGraph() noexcept = default; cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::DepGraph() Line | Count | Source | 70 | 4.34k | DepGraph() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::DepGraph() Line | Count | Source | 70 | 1.36k | DepGraph() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::DepGraph() Line | Count | Source | 70 | 1.36k | DepGraph() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::DepGraph() Line | Count | Source | 70 | 799 | DepGraph() noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::DepGraph() Line | Count | Source | 70 | 750 | DepGraph() noexcept = default; |
|
71 | | DepGraph(const DepGraph&) noexcept = default; |
72 | | DepGraph(DepGraph&&) noexcept = default; |
73 | 336 | DepGraph& operator=(const DepGraph&) noexcept = default; |
74 | 5.10k | DepGraph& operator=(DepGraph&&) noexcept = default; cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::operator=(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>&&) Line | Count | Source | 74 | 2.25k | DepGraph& operator=(DepGraph&&) noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::operator=(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>&&) Line | Count | Source | 74 | 908 | DepGraph& operator=(DepGraph&&) noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::operator=(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>&&) Line | Count | Source | 74 | 908 | DepGraph& operator=(DepGraph&&) noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::operator=(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>&&) Line | Count | Source | 74 | 528 | DepGraph& operator=(DepGraph&&) noexcept = default; |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::operator=(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>&&) Line | Count | Source | 74 | 500 | 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 | 2.81k | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) |
92 | 2.81k | { |
93 | 2.81k | Assume(mapping.size() == depgraph.PositionRange()); |
94 | 2.81k | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); |
95 | 75.1k | for (DepGraphIndex i : depgraph.Positions()) { |
96 | 75.1k | auto new_idx = mapping[i]; |
97 | 75.1k | Assume(new_idx < pos_range); |
98 | | // Add transaction. |
99 | 75.1k | entries[new_idx].ancestors = SetType::Singleton(new_idx); |
100 | 75.1k | entries[new_idx].descendants = SetType::Singleton(new_idx); |
101 | 75.1k | m_used.Set(new_idx); |
102 | | // Fill in fee and size. |
103 | 75.1k | entries[new_idx].feerate = depgraph.entries[i].feerate; |
104 | 75.1k | } |
105 | 75.1k | for (DepGraphIndex i : depgraph.Positions()) { |
106 | | // Fill in dependencies by mapping direct parents. |
107 | 75.1k | SetType parents; |
108 | 224k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); |
109 | 75.1k | AddDependencies(parents, mapping[i]); |
110 | 75.1k | } |
111 | | // Verify that the provided pos_range was correct (no unused positions at the end). |
112 | 2.81k | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); |
113 | 2.81k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::DepGraph(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, std::span<unsigned int const, 18446744073709551615ul>, unsigned int) Line | Count | Source | 91 | 681 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) | 92 | 681 | { | 93 | 681 | Assume(mapping.size() == depgraph.PositionRange()); | 94 | 681 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); | 95 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 96 | 20.8k | auto new_idx = mapping[i]; | 97 | 20.8k | Assume(new_idx < pos_range); | 98 | | // Add transaction. | 99 | 20.8k | entries[new_idx].ancestors = SetType::Singleton(new_idx); | 100 | 20.8k | entries[new_idx].descendants = SetType::Singleton(new_idx); | 101 | 20.8k | m_used.Set(new_idx); | 102 | | // Fill in fee and size. | 103 | 20.8k | entries[new_idx].feerate = depgraph.entries[i].feerate; | 104 | 20.8k | } | 105 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 106 | | // Fill in dependencies by mapping direct parents. | 107 | 20.8k | SetType parents; | 108 | 66.4k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); | 109 | 20.8k | AddDependencies(parents, mapping[i]); | 110 | 20.8k | } | 111 | | // Verify that the provided pos_range was correct (no unused positions at the end). | 112 | 681 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); | 113 | 681 | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::DepGraph(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&, std::span<unsigned int const, 18446744073709551615ul>, unsigned int) Line | Count | Source | 91 | 681 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) | 92 | 681 | { | 93 | 681 | Assume(mapping.size() == depgraph.PositionRange()); | 94 | 681 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); | 95 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 96 | 20.8k | auto new_idx = mapping[i]; | 97 | 20.8k | Assume(new_idx < pos_range); | 98 | | // Add transaction. | 99 | 20.8k | entries[new_idx].ancestors = SetType::Singleton(new_idx); | 100 | 20.8k | entries[new_idx].descendants = SetType::Singleton(new_idx); | 101 | 20.8k | m_used.Set(new_idx); | 102 | | // Fill in fee and size. | 103 | 20.8k | entries[new_idx].feerate = depgraph.entries[i].feerate; | 104 | 20.8k | } | 105 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 106 | | // Fill in dependencies by mapping direct parents. | 107 | 20.8k | SetType parents; | 108 | 66.4k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); | 109 | 20.8k | AddDependencies(parents, mapping[i]); | 110 | 20.8k | } | 111 | | // Verify that the provided pos_range was correct (no unused positions at the end). | 112 | 681 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); | 113 | 681 | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::DepGraph(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&, std::span<unsigned int const, 18446744073709551615ul>, unsigned int) Line | Count | Source | 91 | 681 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) | 92 | 681 | { | 93 | 681 | Assume(mapping.size() == depgraph.PositionRange()); | 94 | 681 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); | 95 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 96 | 20.8k | auto new_idx = mapping[i]; | 97 | 20.8k | Assume(new_idx < pos_range); | 98 | | // Add transaction. | 99 | 20.8k | entries[new_idx].ancestors = SetType::Singleton(new_idx); | 100 | 20.8k | entries[new_idx].descendants = SetType::Singleton(new_idx); | 101 | 20.8k | m_used.Set(new_idx); | 102 | | // Fill in fee and size. | 103 | 20.8k | entries[new_idx].feerate = depgraph.entries[i].feerate; | 104 | 20.8k | } | 105 | 20.8k | for (DepGraphIndex i : depgraph.Positions()) { | 106 | | // Fill in dependencies by mapping direct parents. | 107 | 20.8k | SetType parents; | 108 | 66.4k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); | 109 | 20.8k | AddDependencies(parents, mapping[i]); | 110 | 20.8k | } | 111 | | // Verify that the provided pos_range was correct (no unused positions at the end). | 112 | 681 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); | 113 | 681 | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::DepGraph(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, std::span<unsigned int const, 18446744073709551615ul>, unsigned int) Line | Count | Source | 91 | 396 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) | 92 | 396 | { | 93 | 396 | Assume(mapping.size() == depgraph.PositionRange()); | 94 | 396 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); | 95 | 6.37k | for (DepGraphIndex i : depgraph.Positions()) { | 96 | 6.37k | auto new_idx = mapping[i]; | 97 | 6.37k | Assume(new_idx < pos_range); | 98 | | // Add transaction. | 99 | 6.37k | entries[new_idx].ancestors = SetType::Singleton(new_idx); | 100 | 6.37k | entries[new_idx].descendants = SetType::Singleton(new_idx); | 101 | 6.37k | m_used.Set(new_idx); | 102 | | // Fill in fee and size. | 103 | 6.37k | entries[new_idx].feerate = depgraph.entries[i].feerate; | 104 | 6.37k | } | 105 | 6.37k | for (DepGraphIndex i : depgraph.Positions()) { | 106 | | // Fill in dependencies by mapping direct parents. | 107 | 6.37k | SetType parents; | 108 | 12.7k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); | 109 | 6.37k | AddDependencies(parents, mapping[i]); | 110 | 6.37k | } | 111 | | // Verify that the provided pos_range was correct (no unused positions at the end). | 112 | 396 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); | 113 | 396 | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::DepGraph(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&, std::span<unsigned int const, 18446744073709551615ul>, unsigned int) Line | Count | Source | 91 | 375 | DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range) | 92 | 375 | { | 93 | 375 | Assume(mapping.size() == depgraph.PositionRange()); | 94 | 375 | Assume((pos_range == 0) == (depgraph.TxCount() == 0)); | 95 | 6.31k | for (DepGraphIndex i : depgraph.Positions()) { | 96 | 6.31k | auto new_idx = mapping[i]; | 97 | 6.31k | Assume(new_idx < pos_range); | 98 | | // Add transaction. | 99 | 6.31k | entries[new_idx].ancestors = SetType::Singleton(new_idx); | 100 | 6.31k | entries[new_idx].descendants = SetType::Singleton(new_idx); | 101 | 6.31k | m_used.Set(new_idx); | 102 | | // Fill in fee and size. | 103 | 6.31k | entries[new_idx].feerate = depgraph.entries[i].feerate; | 104 | 6.31k | } | 105 | 6.31k | for (DepGraphIndex i : depgraph.Positions()) { | 106 | | // Fill in dependencies by mapping direct parents. | 107 | 6.31k | SetType parents; | 108 | 12.6k | for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]); | 109 | 6.31k | AddDependencies(parents, mapping[i]); | 110 | 6.31k | } | 111 | | // Verify that the provided pos_range was correct (no unused positions at the end). | 112 | 375 | Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1)); | 113 | 375 | } |
|
114 | | |
115 | | /** Get the set of transactions positions in use. Complexity: O(1). */ |
116 | 5.63M | const SetType& Positions() const noexcept { return m_used; }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Positions() const Line | Count | Source | 116 | 1.83M | const SetType& Positions() const noexcept { return m_used; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Positions() const Line | Count | Source | 116 | 1.44M | const SetType& Positions() const noexcept { return m_used; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Positions() const Line | Count | Source | 116 | 1.44M | const SetType& Positions() const noexcept { return m_used; } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Positions() const Line | Count | Source | 116 | 451k | const SetType& Positions() const noexcept { return m_used; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Positions() const Line | Count | Source | 116 | 451k | 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 | 260k | DepGraphIndex PositionRange() const noexcept { return entries.size(); }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::PositionRange() const Line | Count | Source | 118 | 114k | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::PositionRange() const Line | Count | Source | 118 | 46.9k | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::PositionRange() const Line | Count | Source | 118 | 46.9k | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::PositionRange() const Line | Count | Source | 118 | 25.9k | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::PositionRange() const Line | Count | Source | 118 | 25.8k | DepGraphIndex PositionRange() const noexcept { return entries.size(); } |
|
119 | | /** Get the number of transactions in the graph. Complexity: O(1). */ |
120 | 561k | auto TxCount() const noexcept { return m_used.Count(); }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::TxCount() const Line | Count | Source | 120 | 222k | auto TxCount() const noexcept { return m_used.Count(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::TxCount() const Line | Count | Source | 120 | 112k | auto TxCount() const noexcept { return m_used.Count(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::TxCount() const Line | Count | Source | 120 | 112k | auto TxCount() const noexcept { return m_used.Count(); } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::TxCount() const Line | Count | Source | 120 | 57.0k | auto TxCount() const noexcept { return m_used.Count(); } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::TxCount() const Line | Count | Source | 120 | 56.9k | auto TxCount() const noexcept { return m_used.Count(); } |
|
121 | | /** Get the feerate of a given transaction i. Complexity: O(1). */ |
122 | 23.0M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 7.08M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 6.22M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 6.22M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 1.75M | const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::FeeRate(unsigned int) const Line | Count | Source | 122 | 1.75M | 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 | 478 | FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; } |
125 | | /** Get the ancestors of a given transaction i. Complexity: O(1). */ |
126 | 48.0M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 15.3M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 13.1M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 13.1M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 3.18M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Ancestors(unsigned int) const Line | Count | Source | 126 | 3.18M | const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; } |
|
127 | | /** Get the descendants of a given transaction i. Complexity: O(1). */ |
128 | 2.97M | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::Descendants(unsigned int) const Line | Count | Source | 128 | 1.69M | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::Descendants(unsigned int) const Line | Count | Source | 128 | 547k | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::Descendants(unsigned int) const Line | Count | Source | 128 | 547k | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::Descendants(unsigned int) const Line | Count | Source | 128 | 95.5k | const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::Descendants(unsigned int) const Line | Count | Source | 128 | 95.2k | 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 | 82.1k | { |
137 | 82.1k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); |
138 | 82.1k | auto available = ALL_POSITIONS - m_used; |
139 | 82.1k | Assume(available.Any()); |
140 | 82.1k | DepGraphIndex new_idx = available.First(); |
141 | 82.1k | if (new_idx == entries.size()) { |
142 | 82.1k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); |
143 | 82.1k | } else { |
144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); |
145 | 0 | } |
146 | 82.1k | m_used.Set(new_idx); |
147 | 82.1k | return new_idx; |
148 | 82.1k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 27.8k | { | 137 | 27.8k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 27.8k | auto available = ALL_POSITIONS - m_used; | 139 | 27.8k | Assume(available.Any()); | 140 | 27.8k | DepGraphIndex new_idx = available.First(); | 141 | 27.8k | if (new_idx == entries.size()) { | 142 | 27.8k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 27.8k | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 27.8k | m_used.Set(new_idx); | 147 | 27.8k | return new_idx; | 148 | 27.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 20.8k | { | 137 | 20.8k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 20.8k | auto available = ALL_POSITIONS - m_used; | 139 | 20.8k | Assume(available.Any()); | 140 | 20.8k | DepGraphIndex new_idx = available.First(); | 141 | 20.8k | if (new_idx == entries.size()) { | 142 | 20.8k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 20.8k | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 20.8k | m_used.Set(new_idx); | 147 | 20.8k | return new_idx; | 148 | 20.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 20.8k | { | 137 | 20.8k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 20.8k | auto available = ALL_POSITIONS - m_used; | 139 | 20.8k | Assume(available.Any()); | 140 | 20.8k | DepGraphIndex new_idx = available.First(); | 141 | 20.8k | if (new_idx == entries.size()) { | 142 | 20.8k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 20.8k | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 20.8k | m_used.Set(new_idx); | 147 | 20.8k | return new_idx; | 148 | 20.8k | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 6.40k | { | 137 | 6.40k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 6.40k | auto available = ALL_POSITIONS - m_used; | 139 | 6.40k | Assume(available.Any()); | 140 | 6.40k | DepGraphIndex new_idx = available.First(); | 141 | 6.40k | if (new_idx == entries.size()) { | 142 | 6.40k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 6.40k | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 6.40k | m_used.Set(new_idx); | 147 | 6.40k | return new_idx; | 148 | 6.40k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::AddTransaction(FeeFrac const&) Line | Count | Source | 136 | 6.31k | { | 137 | 6.31k | static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size()); | 138 | 6.31k | auto available = ALL_POSITIONS - m_used; | 139 | 6.31k | Assume(available.Any()); | 140 | 6.31k | DepGraphIndex new_idx = available.First(); | 141 | 6.31k | if (new_idx == entries.size()) { | 142 | 6.31k | entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 143 | 6.31k | } else { | 144 | 0 | entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx)); | 145 | 0 | } | 146 | 6.31k | m_used.Set(new_idx); | 147 | 6.31k | return new_idx; | 148 | 6.31k | } |
|
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 | 1.14k | { |
161 | 1.14k | m_used -= del; |
162 | | // Remove now-unused trailing entries. |
163 | 5.50k | while (!entries.empty() && !m_used[entries.size() - 1]) { |
164 | 4.36k | entries.pop_back(); |
165 | 4.36k | } |
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 | 1.14k | for (auto& entry : entries) { |
170 | 891 | entry.ancestors &= m_used; |
171 | 891 | entry.descendants &= m_used; |
172 | 891 | } |
173 | 1.14k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::RemoveTransactions(bitset_detail::IntBitSet<unsigned int> const&) Line | Count | Source | 160 | 7 | { | 161 | 7 | m_used -= del; | 162 | | // Remove now-unused trailing entries. | 163 | 7 | while (!entries.empty() && !m_used[entries.size() - 1]) { | 164 | 0 | entries.pop_back(); | 165 | 0 | } | 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 | 29 | for (auto& entry : entries) { | 170 | 29 | entry.ancestors &= m_used; | 171 | 29 | entry.descendants &= m_used; | 172 | 29 | } | 173 | 7 | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::RemoveTransactions(bitset_detail::IntBitSet<unsigned long> const&) Line | Count | Source | 160 | 1.13k | { | 161 | 1.13k | m_used -= del; | 162 | | // Remove now-unused trailing entries. | 163 | 5.50k | while (!entries.empty() && !m_used[entries.size() - 1]) { | 164 | 4.36k | entries.pop_back(); | 165 | 4.36k | } | 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 | 1.13k | for (auto& entry : entries) { | 170 | 862 | entry.ancestors &= m_used; | 171 | 862 | entry.descendants &= m_used; | 172 | 862 | } | 173 | 1.13k | } |
|
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 | 162k | { |
181 | 162k | Assume(m_used[child]); |
182 | 162k | Assume(parents.IsSubsetOf(m_used)); |
183 | | // Compute the ancestors of parents that are not already ancestors of child. |
184 | 162k | SetType par_anc; |
185 | 641k | for (auto par : parents - Ancestors(child)) { |
186 | 641k | par_anc |= Ancestors(par); |
187 | 641k | } |
188 | 162k | par_anc -= Ancestors(child); |
189 | | // Bail out if there are no such ancestors. |
190 | 162k | if (par_anc.None()) return; |
191 | | // To each such ancestor, add as descendants the descendants of the child. |
192 | 123k | const auto& chl_des = entries[child].descendants; |
193 | 869k | for (auto anc_of_par : par_anc) { |
194 | 869k | entries[anc_of_par].descendants |= chl_des; |
195 | 869k | } |
196 | | // To each descendant of the child, add those ancestors. |
197 | 123k | for (auto dec_of_chl : Descendants(child)) { |
198 | 123k | entries[dec_of_chl].ancestors |= par_anc; |
199 | 123k | } |
200 | 123k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::AddDependencies(bitset_detail::IntBitSet<unsigned long> const&, unsigned int) Line | Count | Source | 180 | 54.2k | { | 181 | 54.2k | Assume(m_used[child]); | 182 | 54.2k | Assume(parents.IsSubsetOf(m_used)); | 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 54.2k | SetType par_anc; | 185 | 194k | for (auto par : parents - Ancestors(child)) { | 186 | 194k | par_anc |= Ancestors(par); | 187 | 194k | } | 188 | 54.2k | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 54.2k | if (par_anc.None()) return; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 38.0k | const auto& chl_des = entries[child].descendants; | 193 | 292k | for (auto anc_of_par : par_anc) { | 194 | 292k | entries[anc_of_par].descendants |= chl_des; | 195 | 292k | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 38.1k | for (auto dec_of_chl : Descendants(child)) { | 198 | 38.1k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 38.1k | } | 200 | 38.0k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::AddDependencies(bitset_detail::MultiIntBitSet<unsigned int, 2u> const&, unsigned int) Line | Count | Source | 180 | 41.6k | { | 181 | 41.6k | Assume(m_used[child]); | 182 | 41.6k | Assume(parents.IsSubsetOf(m_used)); | 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 41.6k | SetType par_anc; | 185 | 186k | for (auto par : parents - Ancestors(child)) { | 186 | 186k | par_anc |= Ancestors(par); | 187 | 186k | } | 188 | 41.6k | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 41.6k | if (par_anc.None()) return; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 32.8k | const auto& chl_des = entries[child].descendants; | 193 | 240k | for (auto anc_of_par : par_anc) { | 194 | 240k | entries[anc_of_par].descendants |= chl_des; | 195 | 240k | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 32.8k | for (auto dec_of_chl : Descendants(child)) { | 198 | 32.8k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 32.8k | } | 200 | 32.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::AddDependencies(bitset_detail::MultiIntBitSet<unsigned char, 8u> const&, unsigned int) Line | Count | Source | 180 | 41.6k | { | 181 | 41.6k | Assume(m_used[child]); | 182 | 41.6k | Assume(parents.IsSubsetOf(m_used)); | 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 41.6k | SetType par_anc; | 185 | 186k | for (auto par : parents - Ancestors(child)) { | 186 | 186k | par_anc |= Ancestors(par); | 187 | 186k | } | 188 | 41.6k | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 41.6k | if (par_anc.None()) return; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 32.8k | const auto& chl_des = entries[child].descendants; | 193 | 240k | for (auto anc_of_par : par_anc) { | 194 | 240k | entries[anc_of_par].descendants |= chl_des; | 195 | 240k | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 32.8k | for (auto dec_of_chl : Descendants(child)) { | 198 | 32.8k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 32.8k | } | 200 | 32.8k | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::AddDependencies(bitset_detail::IntBitSet<unsigned int> const&, unsigned int) Line | Count | Source | 180 | 12.7k | { | 181 | 12.7k | Assume(m_used[child]); | 182 | 12.7k | Assume(parents.IsSubsetOf(m_used)); | 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 12.7k | SetType par_anc; | 185 | 36.9k | for (auto par : parents - Ancestors(child)) { | 186 | 36.9k | par_anc |= Ancestors(par); | 187 | 36.9k | } | 188 | 12.7k | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 12.7k | if (par_anc.None()) return; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 9.89k | const auto& chl_des = entries[child].descendants; | 193 | 48.4k | for (auto anc_of_par : par_anc) { | 194 | 48.4k | entries[anc_of_par].descendants |= chl_des; | 195 | 48.4k | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 9.89k | for (auto dec_of_chl : Descendants(child)) { | 198 | 9.89k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 9.89k | } | 200 | 9.89k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::AddDependencies(bitset_detail::MultiIntBitSet<unsigned char, 4u> const&, unsigned int) Line | Count | Source | 180 | 12.6k | { | 181 | 12.6k | Assume(m_used[child]); | 182 | 12.6k | Assume(parents.IsSubsetOf(m_used)); | 183 | | // Compute the ancestors of parents that are not already ancestors of child. | 184 | 12.6k | SetType par_anc; | 185 | 36.8k | for (auto par : parents - Ancestors(child)) { | 186 | 36.8k | par_anc |= Ancestors(par); | 187 | 36.8k | } | 188 | 12.6k | par_anc -= Ancestors(child); | 189 | | // Bail out if there are no such ancestors. | 190 | 12.6k | if (par_anc.None()) return; | 191 | | // To each such ancestor, add as descendants the descendants of the child. | 192 | 9.82k | const auto& chl_des = entries[child].descendants; | 193 | 48.3k | for (auto anc_of_par : par_anc) { | 194 | 48.3k | entries[anc_of_par].descendants |= chl_des; | 195 | 48.3k | } | 196 | | // To each descendant of the child, add those ancestors. | 197 | 9.82k | for (auto dec_of_chl : Descendants(child)) { | 198 | 9.82k | entries[dec_of_chl].ancestors |= par_anc; | 199 | 9.82k | } | 200 | 9.82k | } |
|
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 | 5.19M | { |
212 | 5.19M | SetType parents = Ancestors(i); |
213 | 5.19M | parents.Reset(i); |
214 | 26.1M | for (auto parent : parents) { |
215 | 26.1M | if (parents[parent]) { |
216 | 22.1M | parents -= Ancestors(parent); |
217 | 22.1M | parents.Set(parent); |
218 | 22.1M | } |
219 | 26.1M | } |
220 | 5.19M | return parents; |
221 | 5.19M | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 1.49M | { | 212 | 1.49M | SetType parents = Ancestors(i); | 213 | 1.49M | parents.Reset(i); | 214 | 9.00M | for (auto parent : parents) { | 215 | 9.00M | if (parents[parent]) { | 216 | 7.00M | parents -= Ancestors(parent); | 217 | 7.00M | parents.Set(parent); | 218 | 7.00M | } | 219 | 9.00M | } | 220 | 1.49M | return parents; | 221 | 1.49M | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 1.42M | { | 212 | 1.42M | SetType parents = Ancestors(i); | 213 | 1.42M | parents.Reset(i); | 214 | 7.57M | for (auto parent : parents) { | 215 | 7.57M | if (parents[parent]) { | 216 | 6.22M | parents -= Ancestors(parent); | 217 | 6.22M | parents.Set(parent); | 218 | 6.22M | } | 219 | 7.57M | } | 220 | 1.42M | return parents; | 221 | 1.42M | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 1.42M | { | 212 | 1.42M | SetType parents = Ancestors(i); | 213 | 1.42M | parents.Reset(i); | 214 | 6.52M | for (auto parent : parents) { | 215 | 6.52M | if (parents[parent]) { | 216 | 6.22M | parents -= Ancestors(parent); | 217 | 6.22M | parents.Set(parent); | 218 | 6.22M | } | 219 | 6.52M | } | 220 | 1.42M | return parents; | 221 | 1.42M | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 431k | { | 212 | 431k | SetType parents = Ancestors(i); | 213 | 431k | parents.Reset(i); | 214 | 1.65M | for (auto parent : parents) { | 215 | 1.65M | if (parents[parent]) { | 216 | 1.33M | parents -= Ancestors(parent); | 217 | 1.33M | parents.Set(parent); | 218 | 1.33M | } | 219 | 1.65M | } | 220 | 431k | return parents; | 221 | 431k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::GetReducedParents(unsigned int) const Line | Count | Source | 211 | 431k | { | 212 | 431k | SetType parents = Ancestors(i); | 213 | 431k | parents.Reset(i); | 214 | 1.43M | for (auto parent : parents) { | 215 | 1.43M | if (parents[parent]) { | 216 | 1.33M | parents -= Ancestors(parent); | 217 | 1.33M | parents.Set(parent); | 218 | 1.33M | } | 219 | 1.43M | } | 220 | 431k | return parents; | 221 | 431k | } |
|
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 | 50.0k | { |
233 | 50.0k | SetType children = Descendants(i); |
234 | 50.0k | children.Reset(i); |
235 | 233k | for (auto child : children) { |
236 | 233k | if (children[child]) { |
237 | 167k | children -= Descendants(child); |
238 | 167k | children.Set(child); |
239 | 167k | } |
240 | 233k | } |
241 | 50.0k | return children; |
242 | 50.0k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::GetReducedChildren(unsigned int) const Line | Count | Source | 232 | 13.8k | { | 233 | 13.8k | SetType children = Descendants(i); | 234 | 13.8k | children.Reset(i); | 235 | 80.1k | for (auto child : children) { | 236 | 80.1k | if (children[child]) { | 237 | 49.4k | children -= Descendants(child); | 238 | 49.4k | children.Set(child); | 239 | 49.4k | } | 240 | 80.1k | } | 241 | 13.8k | return children; | 242 | 13.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::GetReducedChildren(unsigned int) const Line | Count | Source | 232 | 13.8k | { | 233 | 13.8k | SetType children = Descendants(i); | 234 | 13.8k | children.Reset(i); | 235 | 70.5k | for (auto child : children) { | 236 | 70.5k | if (children[child]) { | 237 | 49.4k | children -= Descendants(child); | 238 | 49.4k | children.Set(child); | 239 | 49.4k | } | 240 | 70.5k | } | 241 | 13.8k | return children; | 242 | 13.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::GetReducedChildren(unsigned int) const Line | Count | Source | 232 | 13.8k | { | 233 | 13.8k | SetType children = Descendants(i); | 234 | 13.8k | children.Reset(i); | 235 | 55.2k | for (auto child : children) { | 236 | 55.2k | if (children[child]) { | 237 | 49.4k | children -= Descendants(child); | 238 | 49.4k | children.Set(child); | 239 | 49.4k | } | 240 | 55.2k | } | 241 | 13.8k | return children; | 242 | 13.8k | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::GetReducedChildren(unsigned int) const Line | Count | Source | 232 | 4.25k | { | 233 | 4.25k | SetType children = Descendants(i); | 234 | 4.25k | children.Reset(i); | 235 | 16.1k | for (auto child : children) { | 236 | 16.1k | if (children[child]) { | 237 | 9.50k | children -= Descendants(child); | 238 | 9.50k | children.Set(child); | 239 | 9.50k | } | 240 | 16.1k | } | 241 | 4.25k | return children; | 242 | 4.25k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::GetReducedChildren(unsigned int) const Line | Count | Source | 232 | 4.21k | { | 233 | 4.21k | SetType children = Descendants(i); | 234 | 4.21k | children.Reset(i); | 235 | 11.6k | for (auto child : children) { | 236 | 11.6k | if (children[child]) { | 237 | 9.47k | children -= Descendants(child); | 238 | 9.47k | children.Set(child); | 239 | 9.47k | } | 240 | 11.6k | } | 241 | 4.21k | return children; | 242 | 4.21k | } |
|
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 | | { |
250 | | FeeFrac ret; |
251 | | for (auto pos : elems) ret += entries[pos].feerate; |
252 | | return ret; |
253 | | } |
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 | 231k | { |
267 | 231k | Assume(todo[tx]); |
268 | 231k | Assume(todo.IsSubsetOf(m_used)); |
269 | 231k | auto to_add = SetType::Singleton(tx); |
270 | 231k | SetType ret; |
271 | 465k | do { |
272 | 465k | SetType old = ret; |
273 | 791k | for (auto add : to_add) { |
274 | 791k | ret |= Descendants(add); |
275 | 791k | ret |= Ancestors(add); |
276 | 791k | } |
277 | 465k | ret &= todo; |
278 | 465k | to_add = ret - old; |
279 | 465k | } while (to_add.Any()); |
280 | 231k | return ret; |
281 | 231k | } |
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 | 231k | { |
292 | 231k | if (todo.None()) return todo; |
293 | 231k | return GetConnectedComponent(todo, todo.First()); |
294 | 231k | } |
295 | | |
296 | | /** Determine if a subset is connected. |
297 | | * |
298 | | * Complexity: O(subset.Count()). |
299 | | */ |
300 | | bool IsConnected(const SetType& subset) const noexcept |
301 | 230k | { |
302 | 230k | return FindConnectedComponent(subset) == subset; |
303 | 230k | } |
304 | | |
305 | | /** Determine if this entire graph is connected. |
306 | | * |
307 | | * Complexity: O(TxCount()). |
308 | | */ |
309 | | 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 | | { |
317 | | DepGraphIndex old_len = list.size(); |
318 | | for (auto i : select) list.push_back(i); |
319 | | std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept { |
320 | | const auto a_anc_count = entries[a].ancestors.Count(); |
321 | | const auto b_anc_count = entries[b].ancestors.Count(); |
322 | | if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count; |
323 | | return a < b; |
324 | | }); |
325 | | } |
326 | | |
327 | | /** Check if this graph is acyclic. */ |
328 | | bool IsAcyclic() const noexcept |
329 | 55.5k | { |
330 | 255k | for (auto i : Positions()) { |
331 | 255k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { |
332 | 0 | return false; |
333 | 0 | } |
334 | 255k | } |
335 | 55.5k | return true; |
336 | 55.5k | } cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>>::IsAcyclic() const Line | Count | Source | 329 | 54.8k | { | 330 | 237k | for (auto i : Positions()) { | 331 | 237k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 237k | } | 335 | 54.8k | return true; | 336 | 54.8k | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::IsAcyclic() const Line | Count | Source | 329 | 227 | { | 330 | 6.93k | for (auto i : Positions()) { | 331 | 6.93k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 6.93k | } | 335 | 227 | return true; | 336 | 227 | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::IsAcyclic() const Line | Count | Source | 329 | 227 | { | 330 | 6.93k | for (auto i : Positions()) { | 331 | 6.93k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 6.93k | } | 335 | 227 | return true; | 336 | 227 | } |
cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>>::IsAcyclic() const Line | Count | Source | 329 | 132 | { | 330 | 2.12k | for (auto i : Positions()) { | 331 | 2.12k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 2.12k | } | 335 | 132 | return true; | 336 | 132 | } |
cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::IsAcyclic() const Line | Count | Source | 329 | 125 | { | 330 | 2.10k | for (auto i : Positions()) { | 331 | 2.10k | if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) { | 332 | 0 | return false; | 333 | 0 | } | 334 | 2.10k | } | 335 | 125 | return true; | 336 | 125 | } |
|
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 | 6.30k | { |
350 | 6.30k | entries.shrink_to_fit(); |
351 | 6.30k | } |
352 | | |
353 | | size_t DynamicMemoryUsage() const noexcept |
354 | 67.7k | { |
355 | 67.7k | return memusage::DynamicUsage(entries); |
356 | 67.7k | } |
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 | 5.07M | SetInfo() noexcept = default; cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::SetInfo() Line | Count | Source | 369 | 1.45M | SetInfo() noexcept = default; |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::SetInfo() Line | Count | Source | 369 | 1.38M | SetInfo() noexcept = default; |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::SetInfo() Line | Count | Source | 369 | 1.38M | SetInfo() noexcept = default; |
cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::SetInfo() Line | Count | Source | 369 | 421k | SetInfo() noexcept = default; |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::SetInfo() Line | Count | Source | 369 | 421k | SetInfo() noexcept = default; |
|
370 | | |
371 | | /** Construct a SetInfo for a specified set and feerate. */ |
372 | | 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 | 5.37M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, unsigned int) Line | Count | Source | 376 | 1.75M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {} |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&, unsigned int) Line | Count | Source | 376 | 1.38M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {} |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&, unsigned int) Line | Count | Source | 376 | 1.38M | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {} |
cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned int>> const&, unsigned int) Line | Count | Source | 376 | 421k | transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {} |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::SetInfo(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&, unsigned int) Line | Count | Source | 376 | 421k | 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 | | 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 | | { |
385 | | Assume(!transactions[pos]); |
386 | | transactions.Set(pos); |
387 | | feerate += depgraph.FeeRate(pos); |
388 | | } |
389 | | |
390 | | /** Add the transactions of other to this SetInfo (no overlap allowed). */ |
391 | | SetInfo& operator|=(const SetInfo& other) noexcept |
392 | 35.3M | { |
393 | 35.3M | Assume(!transactions.Overlaps(other.transactions)); |
394 | 35.3M | transactions |= other.transactions; |
395 | 35.3M | feerate += other.feerate; |
396 | 35.3M | return *this; |
397 | 35.3M | } cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::operator|=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>> const&) Line | Count | Source | 392 | 10.6M | { | 393 | 10.6M | Assume(!transactions.Overlaps(other.transactions)); | 394 | 10.6M | transactions |= other.transactions; | 395 | 10.6M | feerate += other.feerate; | 396 | 10.6M | return *this; | 397 | 10.6M | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::operator|=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&) Line | Count | Source | 392 | 10.3M | { | 393 | 10.3M | Assume(!transactions.Overlaps(other.transactions)); | 394 | 10.3M | transactions |= other.transactions; | 395 | 10.3M | feerate += other.feerate; | 396 | 10.3M | return *this; | 397 | 10.3M | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::operator|=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&) Line | Count | Source | 392 | 10.3M | { | 393 | 10.3M | Assume(!transactions.Overlaps(other.transactions)); | 394 | 10.3M | transactions |= other.transactions; | 395 | 10.3M | feerate += other.feerate; | 396 | 10.3M | return *this; | 397 | 10.3M | } |
cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::operator|=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>> const&) Line | Count | Source | 392 | 2.01M | { | 393 | 2.01M | Assume(!transactions.Overlaps(other.transactions)); | 394 | 2.01M | transactions |= other.transactions; | 395 | 2.01M | feerate += other.feerate; | 396 | 2.01M | return *this; | 397 | 2.01M | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::operator|=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&) Line | Count | Source | 392 | 2.01M | { | 393 | 2.01M | Assume(!transactions.Overlaps(other.transactions)); | 394 | 2.01M | transactions |= other.transactions; | 395 | 2.01M | feerate += other.feerate; | 396 | 2.01M | return *this; | 397 | 2.01M | } |
|
398 | | |
399 | | /** Remove the transactions of other from this SetInfo (which must be a subset). */ |
400 | | SetInfo& operator-=(const SetInfo& other) noexcept |
401 | 15.8M | { |
402 | 15.8M | Assume(other.transactions.IsSubsetOf(transactions)); |
403 | 15.8M | transactions -= other.transactions; |
404 | 15.8M | feerate -= other.feerate; |
405 | 15.8M | return *this; |
406 | 15.8M | } cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>>::operator-=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned long>> const&) Line | Count | Source | 401 | 4.94M | { | 402 | 4.94M | Assume(other.transactions.IsSubsetOf(transactions)); | 403 | 4.94M | transactions -= other.transactions; | 404 | 4.94M | feerate -= other.feerate; | 405 | 4.94M | return *this; | 406 | 4.94M | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>>::operator-=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&) Line | Count | Source | 401 | 4.73M | { | 402 | 4.73M | Assume(other.transactions.IsSubsetOf(transactions)); | 403 | 4.73M | transactions -= other.transactions; | 404 | 4.73M | feerate -= other.feerate; | 405 | 4.73M | return *this; | 406 | 4.73M | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>>::operator-=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&) Line | Count | Source | 401 | 4.73M | { | 402 | 4.73M | Assume(other.transactions.IsSubsetOf(transactions)); | 403 | 4.73M | transactions -= other.transactions; | 404 | 4.73M | feerate -= other.feerate; | 405 | 4.73M | return *this; | 406 | 4.73M | } |
cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>>::operator-=(cluster_linearize::SetInfo<bitset_detail::IntBitSet<unsigned int>> const&) Line | Count | Source | 401 | 702k | { | 402 | 702k | Assume(other.transactions.IsSubsetOf(transactions)); | 403 | 702k | transactions -= other.transactions; | 404 | 702k | feerate -= other.feerate; | 405 | 702k | return *this; | 406 | 702k | } |
cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>>::operator-=(cluster_linearize::SetInfo<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&) Line | Count | Source | 401 | 710k | { | 402 | 710k | Assume(other.transactions.IsSubsetOf(transactions)); | 403 | 710k | transactions -= other.transactions; | 404 | 710k | feerate -= other.feerate; | 405 | 710k | return *this; | 406 | 710k | } |
|
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 | | 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 | 60.3k | { |
430 | 60.3k | std::vector<SetInfo<SetType>> ret; |
431 | 303k | for (DepGraphIndex i : linearization) { |
432 | | /** The new chunk to be added, initially a singleton. */ |
433 | 303k | 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 | 331k | while (!ret.empty() && new_chunk.feerate >> ret.back().feerate) { |
436 | 27.1k | new_chunk |= ret.back(); |
437 | 27.1k | ret.pop_back(); |
438 | 27.1k | } |
439 | | // Actually move that new chunk into the chunking. |
440 | 303k | ret.emplace_back(std::move(new_chunk)); |
441 | 303k | } |
442 | 60.3k | return ret; |
443 | 60.3k | } |
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 | 418 | { |
450 | 418 | std::vector<FeeFrac> ret; |
451 | 2.15k | for (DepGraphIndex i : linearization) { |
452 | | /** The new chunk to be added, initially a singleton. */ |
453 | 2.15k | 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 | 3.03k | while (!ret.empty() && new_chunk >> ret.back()) { |
456 | 883 | new_chunk += ret.back(); |
457 | 883 | ret.pop_back(); |
458 | 883 | } |
459 | | // Actually move that new chunk into the chunking. |
460 | 2.15k | ret.push_back(std::move(new_chunk)); |
461 | 2.15k | } |
462 | 418 | return ret; |
463 | 418 | } |
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 | 191k | inline void InitializeBegin() noexcept {} |
502 | | inline void InitializeEnd(int num_txns, int num_deps) noexcept |
503 | 191k | { |
504 | | // Cost of initialization. |
505 | 191k | m_cost += 39 * num_txns; |
506 | | // Cost of producing linearization at the end. |
507 | 191k | m_cost += 48 * num_txns + 4 * num_deps; |
508 | 191k | } |
509 | 191k | inline void GetLinearizationBegin() noexcept {} |
510 | | inline void GetLinearizationEnd(int num_txns, int num_deps) noexcept |
511 | 191k | { |
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 | 191k | } |
517 | 98.8k | inline void MakeTopologicalBegin() noexcept {} |
518 | | inline void MakeTopologicalEnd(int num_chunks, int num_steps) noexcept |
519 | 98.8k | { |
520 | 98.8k | m_cost += 20 * num_chunks + 28 * num_steps; |
521 | 98.8k | } |
522 | 191k | inline void StartOptimizingBegin() noexcept {} |
523 | 191k | inline void StartOptimizingEnd(int num_chunks) noexcept { m_cost += 13 * num_chunks; } |
524 | 4.58M | inline void ActivateBegin() noexcept {} |
525 | 4.58M | inline void ActivateEnd(int num_deps) noexcept { m_cost += 10 * num_deps + 1; } |
526 | 1.30M | inline void DeactivateBegin() noexcept {} |
527 | 1.30M | inline void DeactivateEnd(int num_deps) noexcept { m_cost += 11 * num_deps + 8; } |
528 | 4.58M | inline void MergeChunksBegin() noexcept {} |
529 | 4.58M | inline void MergeChunksMid(int num_txns) noexcept { m_cost += 2 * num_txns; } |
530 | 4.58M | inline void MergeChunksEnd(int num_steps) noexcept { m_cost += 3 * num_steps + 5; } |
531 | 9.21M | inline void PickMergeCandidateBegin() noexcept {} |
532 | 9.21M | inline void PickMergeCandidateEnd(int num_steps) noexcept { m_cost += 8 * num_steps; } |
533 | 2.55M | inline void PickChunkToOptimizeBegin() noexcept {} |
534 | 2.55M | inline void PickChunkToOptimizeEnd(int num_steps) noexcept { m_cost += num_steps + 4; } |
535 | 2.55M | inline void PickDependencyToSplitBegin() noexcept {} |
536 | 2.55M | inline void PickDependencyToSplitEnd(int num_txns) noexcept { m_cost += 8 * num_txns + 9; } |
537 | 191k | inline void StartMinimizingBegin() noexcept {} |
538 | 191k | inline void StartMinimizingEnd(int num_chunks) noexcept { m_cost += 18 * num_chunks; } |
539 | 2.12M | inline void MinimizeStepBegin() noexcept {} |
540 | 2.12M | inline void MinimizeStepMid(int num_txns) noexcept { m_cost += 11 * num_txns + 11; } |
541 | 286k | inline void MinimizeStepEnd(bool split) noexcept { m_cost += 17 * split + 7; } |
542 | | |
543 | 5.07M | 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 | 1.79M | { |
791 | 1.79M | Assume(tx_idxs.Any()); |
792 | 1.79M | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); |
793 | 3.70M | for (auto tx_idx : tx_idxs) { |
794 | 3.70M | if (pos == 0) return tx_idx; |
795 | 1.91M | --pos; |
796 | 1.91M | } |
797 | 0 | Assume(false); |
798 | 0 | return TxIdx(-1); |
799 | 1.79M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::IntBitSet<unsigned long> const&) Line | Count | Source | 790 | 545k | { | 791 | 545k | Assume(tx_idxs.Any()); | 792 | 545k | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 1.10M | for (auto tx_idx : tx_idxs) { | 794 | 1.10M | if (pos == 0) return tx_idx; | 795 | 562k | --pos; | 796 | 562k | } | 797 | 0 | Assume(false); | 798 | 0 | return TxIdx(-1); | 799 | 545k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::MultiIntBitSet<unsigned int, 2u> const&) Line | Count | Source | 790 | 484k | { | 791 | 484k | Assume(tx_idxs.Any()); | 792 | 484k | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 987k | for (auto tx_idx : tx_idxs) { | 794 | 987k | if (pos == 0) return tx_idx; | 795 | 502k | --pos; | 796 | 502k | } | 797 | 0 | Assume(false); | 798 | 0 | return TxIdx(-1); | 799 | 484k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::MultiIntBitSet<unsigned char, 8u> const&) Line | Count | Source | 790 | 484k | { | 791 | 484k | Assume(tx_idxs.Any()); | 792 | 484k | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 989k | for (auto tx_idx : tx_idxs) { | 794 | 989k | if (pos == 0) return tx_idx; | 795 | 504k | --pos; | 796 | 504k | } | 797 | 0 | Assume(false); | 798 | 0 | return TxIdx(-1); | 799 | 484k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::IntBitSet<unsigned int> const&) Line | Count | Source | 790 | 138k | { | 791 | 138k | Assume(tx_idxs.Any()); | 792 | 138k | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 310k | for (auto tx_idx : tx_idxs) { | 794 | 310k | if (pos == 0) return tx_idx; | 795 | 171k | --pos; | 796 | 171k | } | 797 | 0 | Assume(false); | 798 | 0 | return TxIdx(-1); | 799 | 138k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::PickRandomTx(bitset_detail::MultiIntBitSet<unsigned char, 4u> const&) Line | Count | Source | 790 | 138k | { | 791 | 138k | Assume(tx_idxs.Any()); | 792 | 138k | unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count()); | 793 | 309k | for (auto tx_idx : tx_idxs) { | 794 | 309k | if (pos == 0) return tx_idx; | 795 | 170k | --pos; | 796 | 170k | } | 797 | 0 | Assume(false); | 798 | 0 | return TxIdx(-1); | 799 | 138k | } |
|
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 | | { |
806 | | SetType parents, children; |
807 | | for (auto tx_idx : tx_idxs) { |
808 | | const auto& tx_data = m_tx_data[tx_idx]; |
809 | | parents |= tx_data.parents; |
810 | | children |= tx_data.children; |
811 | | } |
812 | | return {parents - tx_idxs, children - tx_idxs}; |
813 | | } |
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 | 4.58M | { |
819 | 4.58M | m_cost.ActivateBegin(); |
820 | | // Gather and check information about the parent and child transactions. |
821 | 4.58M | auto& parent_data = m_tx_data[parent_idx]; |
822 | 4.58M | auto& child_data = m_tx_data[child_idx]; |
823 | 4.58M | Assume(parent_data.children[child_idx]); |
824 | 4.58M | Assume(!parent_data.active_children[child_idx]); |
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 | 4.58M | auto parent_chunk_idx = parent_data.chunk_idx; |
829 | 4.58M | auto child_chunk_idx = child_data.chunk_idx; |
830 | 4.58M | Assume(parent_chunk_idx != child_chunk_idx); |
831 | 4.58M | Assume(m_chunk_idxs[parent_chunk_idx]); |
832 | 4.58M | Assume(m_chunk_idxs[child_chunk_idx]); |
833 | 4.58M | auto& top_info = m_set_info[parent_chunk_idx]; |
834 | 4.58M | 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 | 39.0M | for (auto tx_idx : top_info.transactions) { |
855 | 39.0M | auto& tx_data = m_tx_data[tx_idx]; |
856 | 39.0M | tx_data.chunk_idx = child_chunk_idx; |
857 | 39.0M | for (auto dep_child_idx : tx_data.active_children) { |
858 | 34.4M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
859 | 34.4M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; |
860 | 34.4M | } |
861 | 39.0M | } |
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 | 19.9M | for (auto tx_idx : bottom_info.transactions) { |
865 | 19.9M | auto& tx_data = m_tx_data[tx_idx]; |
866 | 19.9M | for (auto dep_child_idx : tx_data.active_children) { |
867 | 15.3M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
868 | 15.3M | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; |
869 | 15.3M | } |
870 | 19.9M | } |
871 | | // Merge top_info into bottom_info, which becomes the merged chunk. |
872 | 4.58M | bottom_info |= top_info; |
873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input |
874 | | // chunks' reachable sets. |
875 | 4.58M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; |
876 | 4.58M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; |
877 | 4.58M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; |
878 | 4.58M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; |
879 | | // Make parent chunk the set for the new active dependency. |
880 | 4.58M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; |
881 | 4.58M | parent_data.active_children.Set(child_idx); |
882 | 4.58M | m_chunk_idxs.Reset(parent_chunk_idx); |
883 | | // Return the newly merged chunk. |
884 | 4.58M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); |
885 | 4.58M | return child_chunk_idx; |
886 | 4.58M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 1.31M | { | 819 | 1.31M | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 1.31M | auto& parent_data = m_tx_data[parent_idx]; | 822 | 1.31M | auto& child_data = m_tx_data[child_idx]; | 823 | 1.31M | Assume(parent_data.children[child_idx]); | 824 | 1.31M | Assume(!parent_data.active_children[child_idx]); | 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 | 1.31M | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 1.31M | auto child_chunk_idx = child_data.chunk_idx; | 830 | 1.31M | Assume(parent_chunk_idx != child_chunk_idx); | 831 | 1.31M | Assume(m_chunk_idxs[parent_chunk_idx]); | 832 | 1.31M | Assume(m_chunk_idxs[child_chunk_idx]); | 833 | 1.31M | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 1.31M | 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 | 12.1M | for (auto tx_idx : top_info.transactions) { | 855 | 12.1M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 12.1M | tx_data.chunk_idx = child_chunk_idx; | 857 | 12.1M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 10.8M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 10.8M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; | 860 | 10.8M | } | 861 | 12.1M | } | 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 | 5.91M | for (auto tx_idx : bottom_info.transactions) { | 865 | 5.91M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 5.91M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 4.59M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 4.59M | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 4.59M | } | 870 | 5.91M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 1.31M | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 1.31M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 1.31M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 1.31M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 1.31M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 1.31M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 1.31M | parent_data.active_children.Set(child_idx); | 882 | 1.31M | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 1.31M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 1.31M | return child_chunk_idx; | 886 | 1.31M | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 1.25M | { | 819 | 1.25M | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 1.25M | auto& parent_data = m_tx_data[parent_idx]; | 822 | 1.25M | auto& child_data = m_tx_data[child_idx]; | 823 | 1.25M | Assume(parent_data.children[child_idx]); | 824 | 1.25M | Assume(!parent_data.active_children[child_idx]); | 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 | 1.25M | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 1.25M | auto child_chunk_idx = child_data.chunk_idx; | 830 | 1.25M | Assume(parent_chunk_idx != child_chunk_idx); | 831 | 1.25M | Assume(m_chunk_idxs[parent_chunk_idx]); | 832 | 1.25M | Assume(m_chunk_idxs[child_chunk_idx]); | 833 | 1.25M | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 1.25M | 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 | 11.2M | for (auto tx_idx : top_info.transactions) { | 855 | 11.2M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 11.2M | tx_data.chunk_idx = child_chunk_idx; | 857 | 11.2M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 9.96M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 9.96M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; | 860 | 9.96M | } | 861 | 11.2M | } | 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 | 5.86M | for (auto tx_idx : bottom_info.transactions) { | 865 | 5.86M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 5.86M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 4.60M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 4.60M | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 4.60M | } | 870 | 5.86M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 1.25M | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 1.25M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 1.25M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 1.25M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 1.25M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 1.25M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 1.25M | parent_data.active_children.Set(child_idx); | 882 | 1.25M | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 1.25M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 1.25M | return child_chunk_idx; | 886 | 1.25M | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 1.25M | { | 819 | 1.25M | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 1.25M | auto& parent_data = m_tx_data[parent_idx]; | 822 | 1.25M | auto& child_data = m_tx_data[child_idx]; | 823 | 1.25M | Assume(parent_data.children[child_idx]); | 824 | 1.25M | Assume(!parent_data.active_children[child_idx]); | 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 | 1.25M | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 1.25M | auto child_chunk_idx = child_data.chunk_idx; | 830 | 1.25M | Assume(parent_chunk_idx != child_chunk_idx); | 831 | 1.25M | Assume(m_chunk_idxs[parent_chunk_idx]); | 832 | 1.25M | Assume(m_chunk_idxs[child_chunk_idx]); | 833 | 1.25M | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 1.25M | 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 | 11.2M | for (auto tx_idx : top_info.transactions) { | 855 | 11.2M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 11.2M | tx_data.chunk_idx = child_chunk_idx; | 857 | 11.2M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 9.95M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 9.95M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; | 860 | 9.95M | } | 861 | 11.2M | } | 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 | 5.88M | for (auto tx_idx : bottom_info.transactions) { | 865 | 5.88M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 5.88M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 4.62M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 4.62M | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 4.62M | } | 870 | 5.88M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 1.25M | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 1.25M | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 1.25M | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 1.25M | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 1.25M | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 1.25M | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 1.25M | parent_data.active_children.Set(child_idx); | 882 | 1.25M | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 1.25M | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 1.25M | return child_chunk_idx; | 886 | 1.25M | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 377k | { | 819 | 377k | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 377k | auto& parent_data = m_tx_data[parent_idx]; | 822 | 377k | auto& child_data = m_tx_data[child_idx]; | 823 | 377k | Assume(parent_data.children[child_idx]); | 824 | 377k | Assume(!parent_data.active_children[child_idx]); | 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 | 377k | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 377k | auto child_chunk_idx = child_data.chunk_idx; | 830 | 377k | Assume(parent_chunk_idx != child_chunk_idx); | 831 | 377k | Assume(m_chunk_idxs[parent_chunk_idx]); | 832 | 377k | Assume(m_chunk_idxs[child_chunk_idx]); | 833 | 377k | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 377k | 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 | 2.24M | for (auto tx_idx : top_info.transactions) { | 855 | 2.24M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 2.24M | tx_data.chunk_idx = child_chunk_idx; | 857 | 2.24M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 1.87M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 1.87M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; | 860 | 1.87M | } | 861 | 2.24M | } | 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 | 1.13M | for (auto tx_idx : bottom_info.transactions) { | 865 | 1.13M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 1.13M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 752k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 752k | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 752k | } | 870 | 1.13M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 377k | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 377k | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 377k | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 377k | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 377k | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 377k | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 377k | parent_data.active_children.Set(child_idx); | 882 | 377k | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 377k | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 377k | return child_chunk_idx; | 886 | 377k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::Activate(unsigned int, unsigned int) Line | Count | Source | 818 | 379k | { | 819 | 379k | m_cost.ActivateBegin(); | 820 | | // Gather and check information about the parent and child transactions. | 821 | 379k | auto& parent_data = m_tx_data[parent_idx]; | 822 | 379k | auto& child_data = m_tx_data[child_idx]; | 823 | 379k | Assume(parent_data.children[child_idx]); | 824 | 379k | Assume(!parent_data.active_children[child_idx]); | 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 | 379k | auto parent_chunk_idx = parent_data.chunk_idx; | 829 | 379k | auto child_chunk_idx = child_data.chunk_idx; | 830 | 379k | Assume(parent_chunk_idx != child_chunk_idx); | 831 | 379k | Assume(m_chunk_idxs[parent_chunk_idx]); | 832 | 379k | Assume(m_chunk_idxs[child_chunk_idx]); | 833 | 379k | auto& top_info = m_set_info[parent_chunk_idx]; | 834 | 379k | 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 | 2.26M | for (auto tx_idx : top_info.transactions) { | 855 | 2.26M | auto& tx_data = m_tx_data[tx_idx]; | 856 | 2.26M | tx_data.chunk_idx = child_chunk_idx; | 857 | 2.26M | for (auto dep_child_idx : tx_data.active_children) { | 858 | 1.88M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 859 | 1.88M | if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info; | 860 | 1.88M | } | 861 | 2.26M | } | 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 | 1.13M | for (auto tx_idx : bottom_info.transactions) { | 865 | 1.13M | auto& tx_data = m_tx_data[tx_idx]; | 866 | 1.13M | for (auto dep_child_idx : tx_data.active_children) { | 867 | 758k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 868 | 758k | if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info; | 869 | 758k | } | 870 | 1.13M | } | 871 | | // Merge top_info into bottom_info, which becomes the merged chunk. | 872 | 379k | bottom_info |= top_info; | 873 | | // Compute merged sets of reachable transactions from the new chunk, based on the input | 874 | | // chunks' reachable sets. | 875 | 379k | m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first; | 876 | 379k | m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second; | 877 | 379k | m_reachable[child_chunk_idx].first -= bottom_info.transactions; | 878 | 379k | m_reachable[child_chunk_idx].second -= bottom_info.transactions; | 879 | | // Make parent chunk the set for the new active dependency. | 880 | 379k | parent_data.dep_top_idx[child_idx] = parent_chunk_idx; | 881 | 379k | parent_data.active_children.Set(child_idx); | 882 | 379k | m_chunk_idxs.Reset(parent_chunk_idx); | 883 | | // Return the newly merged chunk. | 884 | 379k | m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1); | 885 | 379k | return child_chunk_idx; | 886 | 379k | } |
|
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 | 1.30M | { |
892 | 1.30M | m_cost.DeactivateBegin(); |
893 | | // Gather and check information about the parent transactions. |
894 | 1.30M | auto& parent_data = m_tx_data[parent_idx]; |
895 | 1.30M | Assume(parent_data.children[child_idx]); |
896 | 1.30M | Assume(parent_data.active_children[child_idx]); |
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 | 1.30M | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; |
900 | 1.30M | auto child_chunk_idx = parent_data.chunk_idx; |
901 | 1.30M | Assume(parent_chunk_idx != child_chunk_idx); |
902 | 1.30M | Assume(m_chunk_idxs[child_chunk_idx]); |
903 | 1.30M | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk |
904 | 1.30M | auto& top_info = m_set_info[parent_chunk_idx]; |
905 | 1.30M | auto& bottom_info = m_set_info[child_chunk_idx]; |
906 | | |
907 | | // Remove the active dependency. |
908 | 1.30M | parent_data.active_children.Reset(child_idx); |
909 | 1.30M | m_chunk_idxs.Set(parent_chunk_idx); |
910 | 1.30M | auto ntx = bottom_info.transactions.Count(); |
911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. |
912 | 1.30M | 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 | 1.30M | SetType top_parents, top_children; |
916 | 15.8M | for (auto tx_idx : top_info.transactions) { |
917 | 15.8M | auto& tx_data = m_tx_data[tx_idx]; |
918 | 15.8M | tx_data.chunk_idx = parent_chunk_idx; |
919 | 15.8M | top_parents |= tx_data.parents; |
920 | 15.8M | top_children |= tx_data.children; |
921 | 15.8M | for (auto dep_child_idx : tx_data.active_children) { |
922 | 14.5M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
923 | 14.5M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; |
924 | 14.5M | } |
925 | 15.8M | } |
926 | 1.30M | SetType bottom_parents, bottom_children; |
927 | 12.3M | for (auto tx_idx : bottom_info.transactions) { |
928 | 12.3M | auto& tx_data = m_tx_data[tx_idx]; |
929 | 12.3M | bottom_parents |= tx_data.parents; |
930 | 12.3M | bottom_children |= tx_data.children; |
931 | 12.3M | for (auto dep_child_idx : tx_data.active_children) { |
932 | 11.0M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; |
933 | 11.0M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; |
934 | 11.0M | } |
935 | 12.3M | } |
936 | | // Compute the new sets of reachable transactions for each new chunk, based on the |
937 | | // top/bottom parents and children computed above. |
938 | 1.30M | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; |
939 | 1.30M | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; |
940 | 1.30M | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; |
941 | 1.30M | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; |
942 | | // Return the two new set idxs. |
943 | 1.30M | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); |
944 | 1.30M | return {parent_chunk_idx, child_chunk_idx}; |
945 | 1.30M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 405k | { | 892 | 405k | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 405k | auto& parent_data = m_tx_data[parent_idx]; | 895 | 405k | Assume(parent_data.children[child_idx]); | 896 | 405k | Assume(parent_data.active_children[child_idx]); | 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 | 405k | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 405k | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 405k | Assume(parent_chunk_idx != child_chunk_idx); | 902 | 405k | Assume(m_chunk_idxs[child_chunk_idx]); | 903 | 405k | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk | 904 | 405k | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 405k | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 405k | parent_data.active_children.Reset(child_idx); | 909 | 405k | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 405k | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 405k | 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 | 405k | SetType top_parents, top_children; | 916 | 4.93M | for (auto tx_idx : top_info.transactions) { | 917 | 4.93M | auto& tx_data = m_tx_data[tx_idx]; | 918 | 4.93M | tx_data.chunk_idx = parent_chunk_idx; | 919 | 4.93M | top_parents |= tx_data.parents; | 920 | 4.93M | top_children |= tx_data.children; | 921 | 4.93M | for (auto dep_child_idx : tx_data.active_children) { | 922 | 4.53M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 4.53M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; | 924 | 4.53M | } | 925 | 4.93M | } | 926 | 405k | SetType bottom_parents, bottom_children; | 927 | 3.87M | for (auto tx_idx : bottom_info.transactions) { | 928 | 3.87M | auto& tx_data = m_tx_data[tx_idx]; | 929 | 3.87M | bottom_parents |= tx_data.parents; | 930 | 3.87M | bottom_children |= tx_data.children; | 931 | 3.87M | for (auto dep_child_idx : tx_data.active_children) { | 932 | 3.46M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 3.46M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 3.46M | } | 935 | 3.87M | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 405k | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 405k | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 405k | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 405k | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 405k | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 405k | return {parent_chunk_idx, child_chunk_idx}; | 945 | 405k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 354k | { | 892 | 354k | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 354k | auto& parent_data = m_tx_data[parent_idx]; | 895 | 354k | Assume(parent_data.children[child_idx]); | 896 | 354k | Assume(parent_data.active_children[child_idx]); | 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 | 354k | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 354k | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 354k | Assume(parent_chunk_idx != child_chunk_idx); | 902 | 354k | Assume(m_chunk_idxs[child_chunk_idx]); | 903 | 354k | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk | 904 | 354k | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 354k | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 354k | parent_data.active_children.Reset(child_idx); | 909 | 354k | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 354k | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 354k | 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 | 354k | SetType top_parents, top_children; | 916 | 4.71M | for (auto tx_idx : top_info.transactions) { | 917 | 4.71M | auto& tx_data = m_tx_data[tx_idx]; | 918 | 4.71M | tx_data.chunk_idx = parent_chunk_idx; | 919 | 4.71M | top_parents |= tx_data.parents; | 920 | 4.71M | top_children |= tx_data.children; | 921 | 4.71M | for (auto dep_child_idx : tx_data.active_children) { | 922 | 4.36M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 4.36M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; | 924 | 4.36M | } | 925 | 4.71M | } | 926 | 354k | SetType bottom_parents, bottom_children; | 927 | 3.74M | for (auto tx_idx : bottom_info.transactions) { | 928 | 3.74M | auto& tx_data = m_tx_data[tx_idx]; | 929 | 3.74M | bottom_parents |= tx_data.parents; | 930 | 3.74M | bottom_children |= tx_data.children; | 931 | 3.74M | for (auto dep_child_idx : tx_data.active_children) { | 932 | 3.39M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 3.39M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 3.39M | } | 935 | 3.74M | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 354k | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 354k | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 354k | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 354k | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 354k | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 354k | return {parent_chunk_idx, child_chunk_idx}; | 945 | 354k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 356k | { | 892 | 356k | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 356k | auto& parent_data = m_tx_data[parent_idx]; | 895 | 356k | Assume(parent_data.children[child_idx]); | 896 | 356k | Assume(parent_data.active_children[child_idx]); | 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 | 356k | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 356k | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 356k | Assume(parent_chunk_idx != child_chunk_idx); | 902 | 356k | Assume(m_chunk_idxs[child_chunk_idx]); | 903 | 356k | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk | 904 | 356k | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 356k | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 356k | parent_data.active_children.Reset(child_idx); | 909 | 356k | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 356k | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 356k | 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 | 356k | SetType top_parents, top_children; | 916 | 4.73M | for (auto tx_idx : top_info.transactions) { | 917 | 4.73M | auto& tx_data = m_tx_data[tx_idx]; | 918 | 4.73M | tx_data.chunk_idx = parent_chunk_idx; | 919 | 4.73M | top_parents |= tx_data.parents; | 920 | 4.73M | top_children |= tx_data.children; | 921 | 4.73M | for (auto dep_child_idx : tx_data.active_children) { | 922 | 4.37M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 4.37M | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; | 924 | 4.37M | } | 925 | 4.73M | } | 926 | 356k | SetType bottom_parents, bottom_children; | 927 | 3.74M | for (auto tx_idx : bottom_info.transactions) { | 928 | 3.74M | auto& tx_data = m_tx_data[tx_idx]; | 929 | 3.74M | bottom_parents |= tx_data.parents; | 930 | 3.74M | bottom_children |= tx_data.children; | 931 | 3.74M | for (auto dep_child_idx : tx_data.active_children) { | 932 | 3.38M | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 3.38M | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 3.38M | } | 935 | 3.74M | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 356k | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 356k | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 356k | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 356k | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 356k | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 356k | return {parent_chunk_idx, child_chunk_idx}; | 945 | 356k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 95.4k | { | 892 | 95.4k | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 95.4k | auto& parent_data = m_tx_data[parent_idx]; | 895 | 95.4k | Assume(parent_data.children[child_idx]); | 896 | 95.4k | Assume(parent_data.active_children[child_idx]); | 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 | 95.4k | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 95.4k | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 95.4k | Assume(parent_chunk_idx != child_chunk_idx); | 902 | 95.4k | Assume(m_chunk_idxs[child_chunk_idx]); | 903 | 95.4k | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk | 904 | 95.4k | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 95.4k | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 95.4k | parent_data.active_children.Reset(child_idx); | 909 | 95.4k | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 95.4k | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 95.4k | 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 | 95.4k | SetType top_parents, top_children; | 916 | 710k | for (auto tx_idx : top_info.transactions) { | 917 | 710k | auto& tx_data = m_tx_data[tx_idx]; | 918 | 710k | tx_data.chunk_idx = parent_chunk_idx; | 919 | 710k | top_parents |= tx_data.parents; | 920 | 710k | top_children |= tx_data.children; | 921 | 710k | for (auto dep_child_idx : tx_data.active_children) { | 922 | 614k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 614k | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; | 924 | 614k | } | 925 | 710k | } | 926 | 95.4k | SetType bottom_parents, bottom_children; | 927 | 473k | for (auto tx_idx : bottom_info.transactions) { | 928 | 473k | auto& tx_data = m_tx_data[tx_idx]; | 929 | 473k | bottom_parents |= tx_data.parents; | 930 | 473k | bottom_children |= tx_data.children; | 931 | 473k | for (auto dep_child_idx : tx_data.active_children) { | 932 | 377k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 377k | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 377k | } | 935 | 473k | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 95.4k | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 95.4k | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 95.4k | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 95.4k | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 95.4k | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 95.4k | return {parent_chunk_idx, child_chunk_idx}; | 945 | 95.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::Deactivate(unsigned int, unsigned int) Line | Count | Source | 891 | 96.7k | { | 892 | 96.7k | m_cost.DeactivateBegin(); | 893 | | // Gather and check information about the parent transactions. | 894 | 96.7k | auto& parent_data = m_tx_data[parent_idx]; | 895 | 96.7k | Assume(parent_data.children[child_idx]); | 896 | 96.7k | Assume(parent_data.active_children[child_idx]); | 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 | 96.7k | auto parent_chunk_idx = parent_data.dep_top_idx[child_idx]; | 900 | 96.7k | auto child_chunk_idx = parent_data.chunk_idx; | 901 | 96.7k | Assume(parent_chunk_idx != child_chunk_idx); | 902 | 96.7k | Assume(m_chunk_idxs[child_chunk_idx]); | 903 | 96.7k | Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk | 904 | 96.7k | auto& top_info = m_set_info[parent_chunk_idx]; | 905 | 96.7k | auto& bottom_info = m_set_info[child_chunk_idx]; | 906 | | | 907 | | // Remove the active dependency. | 908 | 96.7k | parent_data.active_children.Reset(child_idx); | 909 | 96.7k | m_chunk_idxs.Set(parent_chunk_idx); | 910 | 96.7k | auto ntx = bottom_info.transactions.Count(); | 911 | | // Subtract the top_info from the bottom_info, as it will become the child chunk. | 912 | 96.7k | 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 | 96.7k | SetType top_parents, top_children; | 916 | 721k | for (auto tx_idx : top_info.transactions) { | 917 | 721k | auto& tx_data = m_tx_data[tx_idx]; | 918 | 721k | tx_data.chunk_idx = parent_chunk_idx; | 919 | 721k | top_parents |= tx_data.parents; | 920 | 721k | top_children |= tx_data.children; | 921 | 721k | for (auto dep_child_idx : tx_data.active_children) { | 922 | 625k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 923 | 625k | if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info; | 924 | 625k | } | 925 | 721k | } | 926 | 96.7k | SetType bottom_parents, bottom_children; | 927 | 479k | for (auto tx_idx : bottom_info.transactions) { | 928 | 479k | auto& tx_data = m_tx_data[tx_idx]; | 929 | 479k | bottom_parents |= tx_data.parents; | 930 | 479k | bottom_children |= tx_data.children; | 931 | 479k | for (auto dep_child_idx : tx_data.active_children) { | 932 | 382k | auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]]; | 933 | 382k | if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info; | 934 | 382k | } | 935 | 479k | } | 936 | | // Compute the new sets of reachable transactions for each new chunk, based on the | 937 | | // top/bottom parents and children computed above. | 938 | 96.7k | m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions; | 939 | 96.7k | m_reachable[parent_chunk_idx].second = top_children - top_info.transactions; | 940 | 96.7k | m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions; | 941 | 96.7k | m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions; | 942 | | // Return the two new set idxs. | 943 | 96.7k | m_cost.DeactivateEnd(/*num_deps=*/ntx - 1); | 944 | 96.7k | return {parent_chunk_idx, child_chunk_idx}; | 945 | 96.7k | } |
|
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 | 4.58M | { |
951 | 4.58M | m_cost.MergeChunksBegin(); |
952 | 4.58M | Assume(m_chunk_idxs[top_idx]); |
953 | 4.58M | Assume(m_chunk_idxs[bottom_idx]); |
954 | 4.58M | auto& top_chunk_info = m_set_info[top_idx]; |
955 | 4.58M | auto& bottom_chunk_info = m_set_info[bottom_idx]; |
956 | | // Count the number of dependencies between bottom_chunk and top_chunk. |
957 | 4.58M | unsigned num_deps{0}; |
958 | 39.0M | for (auto tx_idx : top_chunk_info.transactions) { |
959 | 39.0M | auto& tx_data = m_tx_data[tx_idx]; |
960 | 39.0M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); |
961 | 39.0M | } |
962 | 4.58M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); |
963 | 4.58M | Assume(num_deps > 0); |
964 | | // Uniformly randomly pick one of them and activate it. |
965 | 4.58M | unsigned pick = m_rng.randrange(num_deps); |
966 | 4.58M | unsigned num_steps = 0; |
967 | 16.3M | for (auto tx_idx : top_chunk_info.transactions) { |
968 | 16.3M | ++num_steps; |
969 | 16.3M | auto& tx_data = m_tx_data[tx_idx]; |
970 | 16.3M | auto intersect = tx_data.children & bottom_chunk_info.transactions; |
971 | 16.3M | auto count = intersect.Count(); |
972 | 16.3M | if (pick < count) { |
973 | 5.99M | for (auto child_idx : intersect) { |
974 | 5.99M | if (pick == 0) { |
975 | 4.58M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); |
976 | 4.58M | return Activate(tx_idx, child_idx); |
977 | 4.58M | } |
978 | 1.40M | --pick; |
979 | 1.40M | } |
980 | 0 | Assume(false); |
981 | 0 | break; |
982 | 4.58M | } |
983 | 11.7M | pick -= count; |
984 | 11.7M | } |
985 | 0 | Assume(false); |
986 | 0 | return INVALID_SET_IDX; |
987 | 4.58M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 1.31M | { | 951 | 1.31M | m_cost.MergeChunksBegin(); | 952 | 1.31M | Assume(m_chunk_idxs[top_idx]); | 953 | 1.31M | Assume(m_chunk_idxs[bottom_idx]); | 954 | 1.31M | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 1.31M | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 1.31M | unsigned num_deps{0}; | 958 | 12.1M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 12.1M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 12.1M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 12.1M | } | 962 | 1.31M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 1.31M | Assume(num_deps > 0); | 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 1.31M | unsigned pick = m_rng.randrange(num_deps); | 966 | 1.31M | unsigned num_steps = 0; | 967 | 5.30M | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 5.30M | ++num_steps; | 969 | 5.30M | auto& tx_data = m_tx_data[tx_idx]; | 970 | 5.30M | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 5.30M | auto count = intersect.Count(); | 972 | 5.30M | if (pick < count) { | 973 | 1.74M | for (auto child_idx : intersect) { | 974 | 1.74M | if (pick == 0) { | 975 | 1.31M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 1.31M | return Activate(tx_idx, child_idx); | 977 | 1.31M | } | 978 | 431k | --pick; | 979 | 431k | } | 980 | 0 | Assume(false); | 981 | 0 | break; | 982 | 1.31M | } | 983 | 3.99M | pick -= count; | 984 | 3.99M | } | 985 | 0 | Assume(false); | 986 | 0 | return INVALID_SET_IDX; | 987 | 1.31M | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 1.25M | { | 951 | 1.25M | m_cost.MergeChunksBegin(); | 952 | 1.25M | Assume(m_chunk_idxs[top_idx]); | 953 | 1.25M | Assume(m_chunk_idxs[bottom_idx]); | 954 | 1.25M | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 1.25M | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 1.25M | unsigned num_deps{0}; | 958 | 11.2M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 11.2M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 11.2M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 11.2M | } | 962 | 1.25M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 1.25M | Assume(num_deps > 0); | 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 1.25M | unsigned pick = m_rng.randrange(num_deps); | 966 | 1.25M | unsigned num_steps = 0; | 967 | 4.53M | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 4.53M | ++num_steps; | 969 | 4.53M | auto& tx_data = m_tx_data[tx_idx]; | 970 | 4.53M | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 4.53M | auto count = intersect.Count(); | 972 | 4.53M | if (pick < count) { | 973 | 1.68M | for (auto child_idx : intersect) { | 974 | 1.68M | if (pick == 0) { | 975 | 1.25M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 1.25M | return Activate(tx_idx, child_idx); | 977 | 1.25M | } | 978 | 429k | --pick; | 979 | 429k | } | 980 | 0 | Assume(false); | 981 | 0 | break; | 982 | 1.25M | } | 983 | 3.27M | pick -= count; | 984 | 3.27M | } | 985 | 0 | Assume(false); | 986 | 0 | return INVALID_SET_IDX; | 987 | 1.25M | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 1.25M | { | 951 | 1.25M | m_cost.MergeChunksBegin(); | 952 | 1.25M | Assume(m_chunk_idxs[top_idx]); | 953 | 1.25M | Assume(m_chunk_idxs[bottom_idx]); | 954 | 1.25M | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 1.25M | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 1.25M | unsigned num_deps{0}; | 958 | 11.2M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 11.2M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 11.2M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 11.2M | } | 962 | 1.25M | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 1.25M | Assume(num_deps > 0); | 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 1.25M | unsigned pick = m_rng.randrange(num_deps); | 966 | 1.25M | unsigned num_steps = 0; | 967 | 4.53M | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 4.53M | ++num_steps; | 969 | 4.53M | auto& tx_data = m_tx_data[tx_idx]; | 970 | 4.53M | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 4.53M | auto count = intersect.Count(); | 972 | 4.53M | if (pick < count) { | 973 | 1.69M | for (auto child_idx : intersect) { | 974 | 1.69M | if (pick == 0) { | 975 | 1.25M | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 1.25M | return Activate(tx_idx, child_idx); | 977 | 1.25M | } | 978 | 433k | --pick; | 979 | 433k | } | 980 | 0 | Assume(false); | 981 | 0 | break; | 982 | 1.25M | } | 983 | 3.27M | pick -= count; | 984 | 3.27M | } | 985 | 0 | Assume(false); | 986 | 0 | return INVALID_SET_IDX; | 987 | 1.25M | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 377k | { | 951 | 377k | m_cost.MergeChunksBegin(); | 952 | 377k | Assume(m_chunk_idxs[top_idx]); | 953 | 377k | Assume(m_chunk_idxs[bottom_idx]); | 954 | 377k | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 377k | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 377k | unsigned num_deps{0}; | 958 | 2.24M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 2.24M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 2.24M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 2.24M | } | 962 | 377k | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 377k | Assume(num_deps > 0); | 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 377k | unsigned pick = m_rng.randrange(num_deps); | 966 | 377k | unsigned num_steps = 0; | 967 | 992k | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 992k | ++num_steps; | 969 | 992k | auto& tx_data = m_tx_data[tx_idx]; | 970 | 992k | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 992k | auto count = intersect.Count(); | 972 | 992k | if (pick < count) { | 973 | 433k | for (auto child_idx : intersect) { | 974 | 433k | if (pick == 0) { | 975 | 377k | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 377k | return Activate(tx_idx, child_idx); | 977 | 377k | } | 978 | 55.3k | --pick; | 979 | 55.3k | } | 980 | 0 | Assume(false); | 981 | 0 | break; | 982 | 377k | } | 983 | 614k | pick -= count; | 984 | 614k | } | 985 | 0 | Assume(false); | 986 | 0 | return INVALID_SET_IDX; | 987 | 377k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeChunks(unsigned char, unsigned char) Line | Count | Source | 950 | 379k | { | 951 | 379k | m_cost.MergeChunksBegin(); | 952 | 379k | Assume(m_chunk_idxs[top_idx]); | 953 | 379k | Assume(m_chunk_idxs[bottom_idx]); | 954 | 379k | auto& top_chunk_info = m_set_info[top_idx]; | 955 | 379k | auto& bottom_chunk_info = m_set_info[bottom_idx]; | 956 | | // Count the number of dependencies between bottom_chunk and top_chunk. | 957 | 379k | unsigned num_deps{0}; | 958 | 2.26M | for (auto tx_idx : top_chunk_info.transactions) { | 959 | 2.26M | auto& tx_data = m_tx_data[tx_idx]; | 960 | 2.26M | num_deps += (tx_data.children & bottom_chunk_info.transactions).Count(); | 961 | 2.26M | } | 962 | 379k | m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count()); | 963 | 379k | Assume(num_deps > 0); | 964 | | // Uniformly randomly pick one of them and activate it. | 965 | 379k | unsigned pick = m_rng.randrange(num_deps); | 966 | 379k | unsigned num_steps = 0; | 967 | 1.00M | for (auto tx_idx : top_chunk_info.transactions) { | 968 | 1.00M | ++num_steps; | 969 | 1.00M | auto& tx_data = m_tx_data[tx_idx]; | 970 | 1.00M | auto intersect = tx_data.children & bottom_chunk_info.transactions; | 971 | 1.00M | auto count = intersect.Count(); | 972 | 1.00M | if (pick < count) { | 973 | 434k | for (auto child_idx : intersect) { | 974 | 434k | if (pick == 0) { | 975 | 379k | m_cost.MergeChunksEnd(/*num_steps=*/num_steps); | 976 | 379k | return Activate(tx_idx, child_idx); | 977 | 379k | } | 978 | 55.5k | --pick; | 979 | 55.5k | } | 980 | 0 | Assume(false); | 981 | 0 | break; | 982 | 379k | } | 983 | 625k | pick -= count; | 984 | 625k | } | 985 | 0 | Assume(false); | 986 | 0 | return INVALID_SET_IDX; | 987 | 379k | } |
|
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 | 3.78M | { |
994 | 3.78M | if constexpr (DownWard) { |
995 | 492k | return MergeChunks(chunk_idx, merge_chunk_idx); |
996 | 3.29M | } else { |
997 | 3.29M | return MergeChunks(merge_chunk_idx, chunk_idx); |
998 | 3.29M | } |
999 | 3.78M | } unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 947k | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 947k | } else { | 997 | 947k | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 947k | } | 999 | 947k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Line | Count | Source | 993 | 139k | { | 994 | 139k | if constexpr (DownWard) { | 995 | 139k | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | | } else { | 997 | | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | | } | 999 | 139k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 891k | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 891k | } else { | 997 | 891k | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 891k | } | 999 | 891k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Line | Count | Source | 993 | 136k | { | 994 | 136k | if constexpr (DownWard) { | 995 | 136k | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | | } else { | 997 | | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | | } | 999 | 136k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 892k | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 892k | } else { | 997 | 892k | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 892k | } | 999 | 892k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Line | Count | Source | 993 | 136k | { | 994 | 136k | if constexpr (DownWard) { | 995 | 136k | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | | } else { | 997 | | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | | } | 999 | 136k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 281k | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 281k | } else { | 997 | 281k | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 281k | } | 999 | 281k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Line | Count | Source | 993 | 40.5k | { | 994 | 40.5k | if constexpr (DownWard) { | 995 | 40.5k | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | | } else { | 997 | | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | | } | 999 | 40.5k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<false>(unsigned char, unsigned char) Line | Count | Source | 993 | 282k | { | 994 | | if constexpr (DownWard) { | 995 | | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | 282k | } else { | 997 | 282k | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | 282k | } | 999 | 282k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeChunksDirected<true>(unsigned char, unsigned char) Line | Count | Source | 993 | 40.1k | { | 994 | 40.1k | if constexpr (DownWard) { | 995 | 40.1k | return MergeChunks(chunk_idx, merge_chunk_idx); | 996 | | } else { | 997 | | return MergeChunks(merge_chunk_idx, chunk_idx); | 998 | | } | 999 | 40.1k | } |
|
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 | 9.21M | { |
1005 | 9.21M | m_cost.PickMergeCandidateBegin(); |
1006 | | /** Information about the chunk. */ |
1007 | 9.21M | Assume(m_chunk_idxs[chunk_idx]); |
1008 | 9.21M | 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 | 9.21M | 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 | 9.21M | 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 | 9.21M | uint64_t best_other_chunk_tiebreak{0}; |
1023 | | |
1024 | | /** Which parent/child transactions we still need to process the chunks for. */ |
1025 | 9.21M | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; |
1026 | 9.21M | unsigned steps = 0; |
1027 | 35.2M | while (todo.Any()) { |
1028 | 26.0M | ++steps; |
1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. |
1030 | 26.0M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; |
1031 | 26.0M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; |
1032 | 26.0M | todo -= reached_chunk_info.transactions; |
1033 | | // See if it has an acceptable feerate. |
1034 | 26.0M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) |
1035 | 26.0M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); |
1036 | 26.0M | if (cmp > 0) continue; |
1037 | 6.97M | uint64_t tiebreak = m_rng.rand64(); |
1038 | 6.97M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { |
1039 | 5.27M | best_other_chunk_feerate = reached_chunk_info.feerate; |
1040 | 5.27M | best_other_chunk_idx = reached_chunk_idx; |
1041 | 5.27M | best_other_chunk_tiebreak = tiebreak; |
1042 | 5.27M | } |
1043 | 6.97M | } |
1044 | 9.21M | Assume(steps <= m_set_info.size()); |
1045 | | |
1046 | 9.21M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); |
1047 | 9.21M | return best_other_chunk_idx; |
1048 | 9.21M | } unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 2.27M | { | 1005 | 2.27M | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 2.27M | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 2.27M | 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 | 2.27M | 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 | 2.27M | 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 | 2.27M | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 2.27M | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 2.27M | unsigned steps = 0; | 1027 | 8.53M | while (todo.Any()) { | 1028 | 6.25M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 6.25M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 6.25M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 6.25M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 6.25M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 6.25M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 6.25M | if (cmp > 0) continue; | 1037 | 1.28M | uint64_t tiebreak = m_rng.rand64(); | 1038 | 1.28M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 1.23M | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 1.23M | best_other_chunk_idx = reached_chunk_idx; | 1041 | 1.23M | best_other_chunk_tiebreak = tiebreak; | 1042 | 1.23M | } | 1043 | 1.28M | } | 1044 | 2.27M | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 2.27M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 2.27M | return best_other_chunk_idx; | 1048 | 2.27M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 389k | { | 1005 | 389k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 389k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 389k | 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 | 389k | 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 | 389k | 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 | 389k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 389k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 389k | unsigned steps = 0; | 1027 | 2.01M | while (todo.Any()) { | 1028 | 1.62M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 1.62M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 1.62M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 1.62M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 1.62M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 1.62M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 1.62M | if (cmp > 0) continue; | 1037 | 745k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 745k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 269k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 269k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 269k | best_other_chunk_tiebreak = tiebreak; | 1042 | 269k | } | 1043 | 745k | } | 1044 | 389k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 389k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 389k | return best_other_chunk_idx; | 1048 | 389k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 2.14M | { | 1005 | 2.14M | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 2.14M | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 2.14M | 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 | 2.14M | 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 | 2.14M | 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 | 2.14M | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 2.14M | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 2.14M | unsigned steps = 0; | 1027 | 8.40M | while (todo.Any()) { | 1028 | 6.25M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 6.25M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 6.25M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 6.25M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 6.25M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 6.25M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 6.25M | if (cmp > 0) continue; | 1037 | 1.23M | uint64_t tiebreak = m_rng.rand64(); | 1038 | 1.23M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 1.18M | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 1.18M | best_other_chunk_idx = reached_chunk_idx; | 1041 | 1.18M | best_other_chunk_tiebreak = tiebreak; | 1042 | 1.18M | } | 1043 | 1.23M | } | 1044 | 2.14M | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 2.14M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 2.14M | return best_other_chunk_idx; | 1048 | 2.14M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 378k | { | 1005 | 378k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 378k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 378k | 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 | 378k | 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 | 378k | 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 | 378k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 378k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 378k | unsigned steps = 0; | 1027 | 1.91M | while (todo.Any()) { | 1028 | 1.54M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 1.54M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 1.54M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 1.54M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 1.54M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 1.54M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 1.54M | if (cmp > 0) continue; | 1037 | 725k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 725k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 261k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 261k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 261k | best_other_chunk_tiebreak = tiebreak; | 1042 | 261k | } | 1043 | 725k | } | 1044 | 378k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 378k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 378k | return best_other_chunk_idx; | 1048 | 378k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 2.14M | { | 1005 | 2.14M | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 2.14M | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 2.14M | 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 | 2.14M | 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 | 2.14M | 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 | 2.14M | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 2.14M | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 2.14M | unsigned steps = 0; | 1027 | 8.39M | while (todo.Any()) { | 1028 | 6.25M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 6.25M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 6.25M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 6.25M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 6.25M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 6.25M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 6.25M | if (cmp > 0) continue; | 1037 | 1.23M | uint64_t tiebreak = m_rng.rand64(); | 1038 | 1.23M | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 1.18M | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 1.18M | best_other_chunk_idx = reached_chunk_idx; | 1041 | 1.18M | best_other_chunk_tiebreak = tiebreak; | 1042 | 1.18M | } | 1043 | 1.23M | } | 1044 | 2.14M | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 2.14M | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 2.14M | return best_other_chunk_idx; | 1048 | 2.14M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 381k | { | 1005 | 381k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 381k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 381k | 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 | 381k | 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 | 381k | 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 | 381k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 381k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 381k | unsigned steps = 0; | 1027 | 1.91M | while (todo.Any()) { | 1028 | 1.53M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 1.53M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 1.53M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 1.53M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 1.53M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 1.53M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 1.53M | if (cmp > 0) continue; | 1037 | 712k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 712k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 260k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 260k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 260k | best_other_chunk_tiebreak = tiebreak; | 1042 | 260k | } | 1043 | 712k | } | 1044 | 381k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 381k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 381k | return best_other_chunk_idx; | 1048 | 381k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 648k | { | 1005 | 648k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 648k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 648k | 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 | 648k | 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 | 648k | 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 | 648k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 648k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 648k | unsigned steps = 0; | 1027 | 1.68M | while (todo.Any()) { | 1028 | 1.03M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 1.03M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 1.03M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 1.03M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 1.03M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 1.03M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 1.03M | if (cmp > 0) continue; | 1037 | 376k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 376k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 366k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 366k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 366k | best_other_chunk_tiebreak = tiebreak; | 1042 | 366k | } | 1043 | 376k | } | 1044 | 648k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 648k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 648k | return best_other_chunk_idx; | 1048 | 648k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 98.8k | { | 1005 | 98.8k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 98.8k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 98.8k | 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 | 98.8k | 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 | 98.8k | 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 | 98.8k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 98.8k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 98.8k | unsigned steps = 0; | 1027 | 369k | while (todo.Any()) { | 1028 | 270k | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 270k | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 270k | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 270k | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 270k | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 270k | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 270k | if (cmp > 0) continue; | 1037 | 145k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 145k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 72.9k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 72.9k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 72.9k | best_other_chunk_tiebreak = tiebreak; | 1042 | 72.9k | } | 1043 | 145k | } | 1044 | 98.8k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 98.8k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 98.8k | return best_other_chunk_idx; | 1048 | 98.8k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<false>(unsigned char) Line | Count | Source | 1004 | 650k | { | 1005 | 650k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 650k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 650k | 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 | 650k | 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 | 650k | 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 | 650k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 650k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 650k | unsigned steps = 0; | 1027 | 1.69M | while (todo.Any()) { | 1028 | 1.04M | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 1.04M | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 1.04M | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 1.04M | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 1.04M | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 1.04M | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 1.04M | if (cmp > 0) continue; | 1037 | 376k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 376k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 366k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 366k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 366k | best_other_chunk_tiebreak = tiebreak; | 1042 | 366k | } | 1043 | 376k | } | 1044 | 650k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 650k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 650k | return best_other_chunk_idx; | 1048 | 650k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::PickMergeCandidate<true>(unsigned char) Line | Count | Source | 1004 | 98.6k | { | 1005 | 98.6k | m_cost.PickMergeCandidateBegin(); | 1006 | | /** Information about the chunk. */ | 1007 | 98.6k | Assume(m_chunk_idxs[chunk_idx]); | 1008 | 98.6k | 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 | 98.6k | 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 | 98.6k | 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 | 98.6k | uint64_t best_other_chunk_tiebreak{0}; | 1023 | | | 1024 | | /** Which parent/child transactions we still need to process the chunks for. */ | 1025 | 98.6k | auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first; | 1026 | 98.6k | unsigned steps = 0; | 1027 | 366k | while (todo.Any()) { | 1028 | 267k | ++steps; | 1029 | | // Find a chunk for a transaction in todo, and remove all its transactions from todo. | 1030 | 267k | auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx; | 1031 | 267k | auto& reached_chunk_info = m_set_info[reached_chunk_idx]; | 1032 | 267k | todo -= reached_chunk_info.transactions; | 1033 | | // See if it has an acceptable feerate. | 1034 | 267k | auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk_info.feerate) | 1035 | 267k | : FeeRateCompare(reached_chunk_info.feerate, best_other_chunk_feerate); | 1036 | 267k | if (cmp > 0) continue; | 1037 | 145k | uint64_t tiebreak = m_rng.rand64(); | 1038 | 145k | if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { | 1039 | 72.6k | best_other_chunk_feerate = reached_chunk_info.feerate; | 1040 | 72.6k | best_other_chunk_idx = reached_chunk_idx; | 1041 | 72.6k | best_other_chunk_tiebreak = tiebreak; | 1042 | 72.6k | } | 1043 | 145k | } | 1044 | 98.6k | Assume(steps <= m_set_info.size()); | 1045 | | | 1046 | 98.6k | m_cost.PickMergeCandidateEnd(/*num_steps=*/steps); | 1047 | 98.6k | return best_other_chunk_idx; | 1048 | 98.6k | } |
|
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 | 9.21M | { |
1055 | 9.21M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); |
1056 | 9.21M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; |
1057 | 3.78M | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); |
1058 | 3.78M | Assume(chunk_idx != INVALID_SET_IDX); |
1059 | 3.78M | return chunk_idx; |
1060 | 9.21M | } unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 2.27M | { | 1055 | 2.27M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 2.27M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 947k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 947k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 947k | return chunk_idx; | 1060 | 2.27M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 389k | { | 1055 | 389k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 389k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 139k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 139k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 139k | return chunk_idx; | 1060 | 389k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 2.14M | { | 1055 | 2.14M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 2.14M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 891k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 891k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 891k | return chunk_idx; | 1060 | 2.14M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 378k | { | 1055 | 378k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 378k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 136k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 136k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 136k | return chunk_idx; | 1060 | 378k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 2.14M | { | 1055 | 2.14M | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 2.14M | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 892k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 892k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 892k | return chunk_idx; | 1060 | 2.14M | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 381k | { | 1055 | 381k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 381k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 136k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 136k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 136k | return chunk_idx; | 1060 | 381k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 648k | { | 1055 | 648k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 648k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 281k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 281k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 281k | return chunk_idx; | 1060 | 648k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 98.8k | { | 1055 | 98.8k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 98.8k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 40.5k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 40.5k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 40.5k | return chunk_idx; | 1060 | 98.8k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<false>(unsigned char) Line | Count | Source | 1054 | 650k | { | 1055 | 650k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 650k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 282k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 282k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 282k | return chunk_idx; | 1060 | 650k | } |
unsigned char cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeStep<true>(unsigned char) Line | Count | Source | 1054 | 98.6k | { | 1055 | 98.6k | auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx); | 1056 | 98.6k | if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX; | 1057 | 40.1k | chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx); | 1058 | 40.1k | Assume(chunk_idx != INVALID_SET_IDX); | 1059 | 40.1k | return chunk_idx; | 1060 | 98.6k | } |
|
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 | 441k | { |
1066 | 441k | Assume(m_chunk_idxs[chunk_idx]); |
1067 | 491k | while (true) { |
1068 | 491k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); |
1069 | 491k | if (merged_chunk_idx == INVALID_SET_IDX) break; |
1070 | 49.3k | chunk_idx = merged_chunk_idx; |
1071 | 49.3k | } |
1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. |
1073 | 441k | if (!m_suboptimal_idxs[chunk_idx]) { |
1074 | 429k | m_suboptimal_idxs.Set(chunk_idx); |
1075 | 429k | m_suboptimal_chunks.push_back(chunk_idx); |
1076 | 429k | } |
1077 | 441k | } void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Line | Count | Source | 1065 | 68.8k | { | 1066 | 68.8k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 74.2k | while (true) { | 1068 | 74.2k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 74.2k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 5.46k | chunk_idx = merged_chunk_idx; | 1071 | 5.46k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 68.8k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 68.8k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 68.8k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 68.8k | } | 1077 | 68.8k | } |
void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Line | Count | Source | 1065 | 68.8k | { | 1066 | 68.8k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 78.5k | while (true) { | 1068 | 78.5k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 78.5k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 9.77k | chunk_idx = merged_chunk_idx; | 1071 | 9.77k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 68.8k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 64.9k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 64.9k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 64.9k | } | 1077 | 68.8k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Line | Count | Source | 1065 | 67.9k | { | 1066 | 67.9k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 73.3k | while (true) { | 1068 | 73.3k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 73.3k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 5.47k | chunk_idx = merged_chunk_idx; | 1071 | 5.47k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 67.9k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 67.9k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 67.9k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 67.9k | } | 1077 | 67.9k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Line | Count | Source | 1065 | 67.9k | { | 1066 | 67.9k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 77.6k | while (true) { | 1068 | 77.6k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 77.6k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 9.71k | chunk_idx = merged_chunk_idx; | 1071 | 9.71k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 67.9k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 64.1k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 64.1k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 64.1k | } | 1077 | 67.9k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Line | Count | Source | 1065 | 68.4k | { | 1066 | 68.4k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 73.9k | while (true) { | 1068 | 73.9k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 73.9k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 5.50k | chunk_idx = merged_chunk_idx; | 1071 | 5.50k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 68.4k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 68.4k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 68.4k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 68.4k | } | 1077 | 68.4k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Line | Count | Source | 1065 | 68.4k | { | 1066 | 68.4k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 78.0k | while (true) { | 1068 | 78.0k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 78.0k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 9.64k | chunk_idx = merged_chunk_idx; | 1071 | 9.64k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 68.4k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 64.7k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 64.7k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 64.7k | } | 1077 | 68.4k | } |
void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Line | Count | Source | 1065 | 7.73k | { | 1066 | 7.73k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 8.32k | while (true) { | 1068 | 8.32k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 8.32k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 587 | chunk_idx = merged_chunk_idx; | 1071 | 587 | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 7.73k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 7.73k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 7.73k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 7.73k | } | 1077 | 7.73k | } |
void cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Line | Count | Source | 1065 | 7.73k | { | 1066 | 7.73k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 9.02k | while (true) { | 1068 | 9.02k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 9.02k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 1.28k | chunk_idx = merged_chunk_idx; | 1071 | 1.28k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 7.73k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 7.28k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 7.28k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 7.28k | } | 1077 | 7.73k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<false>(unsigned char) Line | Count | Source | 1065 | 7.96k | { | 1066 | 7.96k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 8.55k | while (true) { | 1068 | 8.55k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 8.55k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 595 | chunk_idx = merged_chunk_idx; | 1071 | 595 | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 7.96k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 7.96k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 7.96k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 7.96k | } | 1077 | 7.96k | } |
void cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MergeSequence<true>(unsigned char) Line | Count | Source | 1065 | 7.96k | { | 1066 | 7.96k | Assume(m_chunk_idxs[chunk_idx]); | 1067 | 9.23k | while (true) { | 1068 | 9.23k | auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx); | 1069 | 9.23k | if (merged_chunk_idx == INVALID_SET_IDX) break; | 1070 | 1.27k | chunk_idx = merged_chunk_idx; | 1071 | 1.27k | } | 1072 | | // Add the chunk to the queue of improvable chunks, if it wasn't already there. | 1073 | 7.96k | if (!m_suboptimal_idxs[chunk_idx]) { | 1074 | 7.52k | m_suboptimal_idxs.Set(chunk_idx); | 1075 | 7.52k | m_suboptimal_chunks.push_back(chunk_idx); | 1076 | 7.52k | } | 1077 | 7.96k | } |
|
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 | 1.02M | { |
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 | 1.02M | 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 | 1.02M | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; |
1092 | 1.02M | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; |
1093 | 1.02M | 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 | 800k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); |
1103 | 800k | if (!m_suboptimal_idxs[merged_chunk_idx]) { |
1104 | 800k | m_suboptimal_idxs.Set(merged_chunk_idx); |
1105 | 800k | m_suboptimal_chunks.push_back(merged_chunk_idx); |
1106 | 800k | } |
1107 | 800k | } else { |
1108 | | // Merge the top chunk with lower-feerate chunks it depends on. |
1109 | 220k | MergeSequence<false>(parent_chunk_idx); |
1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. |
1111 | 220k | MergeSequence<true>(child_chunk_idx); |
1112 | 220k | } |
1113 | 1.02M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Line | Count | Source | 1082 | 297k | { | 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 | 297k | 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 | 297k | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1092 | 297k | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1093 | 297k | 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 | 228k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1103 | 228k | if (!m_suboptimal_idxs[merged_chunk_idx]) { | 1104 | 228k | m_suboptimal_idxs.Set(merged_chunk_idx); | 1105 | 228k | m_suboptimal_chunks.push_back(merged_chunk_idx); | 1106 | 228k | } | 1107 | 228k | } else { | 1108 | | // Merge the top chunk with lower-feerate chunks it depends on. | 1109 | 68.8k | MergeSequence<false>(parent_chunk_idx); | 1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. | 1111 | 68.8k | MergeSequence<true>(child_chunk_idx); | 1112 | 68.8k | } | 1113 | 297k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Line | Count | Source | 1082 | 297k | { | 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 | 297k | 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 | 297k | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1092 | 297k | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1093 | 297k | 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 | 229k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1103 | 229k | if (!m_suboptimal_idxs[merged_chunk_idx]) { | 1104 | 229k | m_suboptimal_idxs.Set(merged_chunk_idx); | 1105 | 229k | m_suboptimal_chunks.push_back(merged_chunk_idx); | 1106 | 229k | } | 1107 | 229k | } else { | 1108 | | // Merge the top chunk with lower-feerate chunks it depends on. | 1109 | 67.9k | MergeSequence<false>(parent_chunk_idx); | 1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. | 1111 | 67.9k | MergeSequence<true>(child_chunk_idx); | 1112 | 67.9k | } | 1113 | 297k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Line | Count | Source | 1082 | 298k | { | 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 | 298k | 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 | 298k | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1092 | 298k | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1093 | 298k | 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 | 230k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1103 | 230k | if (!m_suboptimal_idxs[merged_chunk_idx]) { | 1104 | 230k | m_suboptimal_idxs.Set(merged_chunk_idx); | 1105 | 230k | m_suboptimal_chunks.push_back(merged_chunk_idx); | 1106 | 230k | } | 1107 | 230k | } else { | 1108 | | // Merge the top chunk with lower-feerate chunks it depends on. | 1109 | 68.4k | MergeSequence<false>(parent_chunk_idx); | 1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. | 1111 | 68.4k | MergeSequence<true>(child_chunk_idx); | 1112 | 68.4k | } | 1113 | 298k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Line | Count | Source | 1082 | 63.6k | { | 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 | 63.6k | 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 | 63.6k | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1092 | 63.6k | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1093 | 63.6k | 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 | 55.9k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1103 | 55.9k | if (!m_suboptimal_idxs[merged_chunk_idx]) { | 1104 | 55.9k | m_suboptimal_idxs.Set(merged_chunk_idx); | 1105 | 55.9k | m_suboptimal_chunks.push_back(merged_chunk_idx); | 1106 | 55.9k | } | 1107 | 55.9k | } else { | 1108 | | // Merge the top chunk with lower-feerate chunks it depends on. | 1109 | 7.73k | MergeSequence<false>(parent_chunk_idx); | 1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. | 1111 | 7.73k | MergeSequence<true>(child_chunk_idx); | 1112 | 7.73k | } | 1113 | 63.6k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::Improve(unsigned int, unsigned int) Line | Count | Source | 1082 | 64.9k | { | 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 | 64.9k | 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 | 64.9k | const auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1092 | 64.9k | const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1093 | 64.9k | 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 | 57.0k | auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx); | 1103 | 57.0k | if (!m_suboptimal_idxs[merged_chunk_idx]) { | 1104 | 57.0k | m_suboptimal_idxs.Set(merged_chunk_idx); | 1105 | 57.0k | m_suboptimal_chunks.push_back(merged_chunk_idx); | 1106 | 57.0k | } | 1107 | 57.0k | } else { | 1108 | | // Merge the top chunk with lower-feerate chunks it depends on. | 1109 | 7.96k | MergeSequence<false>(parent_chunk_idx); | 1110 | | // Merge the bottom chunk with higher-feerate chunks that depend on it. | 1111 | 7.96k | MergeSequence<true>(child_chunk_idx); | 1112 | 7.96k | } | 1113 | 64.9k | } |
|
1114 | | |
1115 | | /** Determine the next chunk to optimize, or INVALID_SET_IDX if none. */ |
1116 | | SetIdx PickChunkToOptimize() noexcept |
1117 | 2.55M | { |
1118 | 2.55M | m_cost.PickChunkToOptimizeBegin(); |
1119 | 2.55M | unsigned steps{0}; |
1120 | 2.56M | while (!m_suboptimal_chunks.empty()) { |
1121 | 2.56M | ++steps; |
1122 | | // Pop an entry from the potentially-suboptimal chunk queue. |
1123 | 2.56M | SetIdx chunk_idx = m_suboptimal_chunks.front(); |
1124 | 2.56M | Assume(m_suboptimal_idxs[chunk_idx]); |
1125 | 2.56M | m_suboptimal_idxs.Reset(chunk_idx); |
1126 | 2.56M | m_suboptimal_chunks.pop_front(); |
1127 | 2.56M | if (m_chunk_idxs[chunk_idx]) { |
1128 | 2.55M | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); |
1129 | 2.55M | return chunk_idx; |
1130 | 2.55M | } |
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 | 2.56M | } |
1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); |
1136 | 0 | return INVALID_SET_IDX; |
1137 | 2.55M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 744k | { | 1118 | 744k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 744k | unsigned steps{0}; | 1120 | 746k | while (!m_suboptimal_chunks.empty()) { | 1121 | 746k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 746k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 746k | Assume(m_suboptimal_idxs[chunk_idx]); | 1125 | 746k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 746k | m_suboptimal_chunks.pop_front(); | 1127 | 746k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 744k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 744k | return chunk_idx; | 1130 | 744k | } | 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 | 746k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 744k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 733k | { | 1118 | 733k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 733k | unsigned steps{0}; | 1120 | 735k | while (!m_suboptimal_chunks.empty()) { | 1121 | 735k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 735k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 735k | Assume(m_suboptimal_idxs[chunk_idx]); | 1125 | 735k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 735k | m_suboptimal_chunks.pop_front(); | 1127 | 735k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 733k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 733k | return chunk_idx; | 1130 | 733k | } | 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 | 735k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 733k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 735k | { | 1118 | 735k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 735k | unsigned steps{0}; | 1120 | 737k | while (!m_suboptimal_chunks.empty()) { | 1121 | 737k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 737k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 737k | Assume(m_suboptimal_idxs[chunk_idx]); | 1125 | 737k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 737k | m_suboptimal_chunks.pop_front(); | 1127 | 737k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 735k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 735k | return chunk_idx; | 1130 | 735k | } | 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 | 737k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 735k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 171k | { | 1118 | 171k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 171k | unsigned steps{0}; | 1120 | 171k | while (!m_suboptimal_chunks.empty()) { | 1121 | 171k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 171k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 171k | Assume(m_suboptimal_idxs[chunk_idx]); | 1125 | 171k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 171k | m_suboptimal_chunks.pop_front(); | 1127 | 171k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 171k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 171k | return chunk_idx; | 1130 | 171k | } | 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 | 171k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 171k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::PickChunkToOptimize() Line | Count | Source | 1117 | 172k | { | 1118 | 172k | m_cost.PickChunkToOptimizeBegin(); | 1119 | 172k | unsigned steps{0}; | 1120 | 173k | while (!m_suboptimal_chunks.empty()) { | 1121 | 173k | ++steps; | 1122 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1123 | 173k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1124 | 173k | Assume(m_suboptimal_idxs[chunk_idx]); | 1125 | 173k | m_suboptimal_idxs.Reset(chunk_idx); | 1126 | 173k | m_suboptimal_chunks.pop_front(); | 1127 | 173k | if (m_chunk_idxs[chunk_idx]) { | 1128 | 172k | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1129 | 172k | return chunk_idx; | 1130 | 172k | } | 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 | 173k | } | 1135 | 0 | m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps); | 1136 | 0 | return INVALID_SET_IDX; | 1137 | 172k | } |
|
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 | 2.55M | { |
1142 | 2.55M | m_cost.PickDependencyToSplitBegin(); |
1143 | 2.55M | Assume(m_chunk_idxs[chunk_idx]); |
1144 | 2.55M | auto& chunk_info = m_set_info[chunk_idx]; |
1145 | | |
1146 | | // Remember the best dependency {par, chl} seen so far. |
1147 | 2.55M | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; |
1148 | 2.55M | uint64_t candidate_tiebreak = 0; |
1149 | | // Iterate over all transactions. |
1150 | 30.1M | for (auto tx_idx : chunk_info.transactions) { |
1151 | 30.1M | const auto& tx_data = m_tx_data[tx_idx]; |
1152 | | // Iterate over all active child dependencies of the transaction. |
1153 | 30.1M | for (auto child_idx : tx_data.active_children) { |
1154 | 27.5M | 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 | 27.5M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); |
1158 | 27.5M | 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 | 4.39M | uint64_t tiebreak = m_rng.rand64(); |
1163 | 4.39M | if (tiebreak < candidate_tiebreak) continue; |
1164 | | // Remember this as our (new) candidate dependency. |
1165 | 1.96M | candidate_dep = {tx_idx, child_idx}; |
1166 | 1.96M | candidate_tiebreak = tiebreak; |
1167 | 1.96M | } |
1168 | 30.1M | } |
1169 | 2.55M | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); |
1170 | 2.55M | return candidate_dep; |
1171 | 2.55M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 744k | { | 1142 | 744k | m_cost.PickDependencyToSplitBegin(); | 1143 | 744k | Assume(m_chunk_idxs[chunk_idx]); | 1144 | 744k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 744k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 744k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 9.16M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 9.16M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 9.16M | for (auto child_idx : tx_data.active_children) { | 1154 | 8.42M | 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 | 8.42M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 8.42M | 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 | 1.36M | uint64_t tiebreak = m_rng.rand64(); | 1163 | 1.36M | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 591k | candidate_dep = {tx_idx, child_idx}; | 1166 | 591k | candidate_tiebreak = tiebreak; | 1167 | 591k | } | 1168 | 9.16M | } | 1169 | 744k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 744k | return candidate_dep; | 1171 | 744k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 733k | { | 1142 | 733k | m_cost.PickDependencyToSplitBegin(); | 1143 | 733k | Assume(m_chunk_idxs[chunk_idx]); | 1144 | 733k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 733k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 733k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 9.12M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 9.12M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 9.12M | for (auto child_idx : tx_data.active_children) { | 1154 | 8.39M | 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 | 8.39M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 8.39M | 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 | 1.36M | uint64_t tiebreak = m_rng.rand64(); | 1163 | 1.36M | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 591k | candidate_dep = {tx_idx, child_idx}; | 1166 | 591k | candidate_tiebreak = tiebreak; | 1167 | 591k | } | 1168 | 9.12M | } | 1169 | 733k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 733k | return candidate_dep; | 1171 | 733k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 735k | { | 1142 | 735k | m_cost.PickDependencyToSplitBegin(); | 1143 | 735k | Assume(m_chunk_idxs[chunk_idx]); | 1144 | 735k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 735k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 735k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 9.13M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 9.13M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 9.13M | for (auto child_idx : tx_data.active_children) { | 1154 | 8.39M | 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 | 8.39M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 8.39M | 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 | 1.37M | uint64_t tiebreak = m_rng.rand64(); | 1163 | 1.37M | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 595k | candidate_dep = {tx_idx, child_idx}; | 1166 | 595k | candidate_tiebreak = tiebreak; | 1167 | 595k | } | 1168 | 9.13M | } | 1169 | 735k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 735k | return candidate_dep; | 1171 | 735k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 171k | { | 1142 | 171k | m_cost.PickDependencyToSplitBegin(); | 1143 | 171k | Assume(m_chunk_idxs[chunk_idx]); | 1144 | 171k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 171k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 171k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 1.33M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 1.33M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 1.33M | for (auto child_idx : tx_data.active_children) { | 1154 | 1.15M | 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 | 1.15M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 1.15M | 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 | 144k | uint64_t tiebreak = m_rng.rand64(); | 1163 | 144k | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 94.9k | candidate_dep = {tx_idx, child_idx}; | 1166 | 94.9k | candidate_tiebreak = tiebreak; | 1167 | 94.9k | } | 1168 | 1.33M | } | 1169 | 171k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 171k | return candidate_dep; | 1171 | 171k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::PickDependencyToSplit(unsigned char) Line | Count | Source | 1141 | 172k | { | 1142 | 172k | m_cost.PickDependencyToSplitBegin(); | 1143 | 172k | Assume(m_chunk_idxs[chunk_idx]); | 1144 | 172k | auto& chunk_info = m_set_info[chunk_idx]; | 1145 | | | 1146 | | // Remember the best dependency {par, chl} seen so far. | 1147 | 172k | std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)}; | 1148 | 172k | uint64_t candidate_tiebreak = 0; | 1149 | | // Iterate over all transactions. | 1150 | 1.34M | for (auto tx_idx : chunk_info.transactions) { | 1151 | 1.34M | const auto& tx_data = m_tx_data[tx_idx]; | 1152 | | // Iterate over all active child dependencies of the transaction. | 1153 | 1.34M | for (auto child_idx : tx_data.active_children) { | 1154 | 1.17M | 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 | 1.17M | auto cmp = FeeRateCompare(dep_top_info.feerate, chunk_info.feerate); | 1158 | 1.17M | 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 | 147k | uint64_t tiebreak = m_rng.rand64(); | 1163 | 147k | if (tiebreak < candidate_tiebreak) continue; | 1164 | | // Remember this as our (new) candidate dependency. | 1165 | 97.1k | candidate_dep = {tx_idx, child_idx}; | 1166 | 97.1k | candidate_tiebreak = tiebreak; | 1167 | 97.1k | } | 1168 | 1.34M | } | 1169 | 172k | m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count()); | 1170 | 172k | return candidate_dep; | 1171 | 172k | } |
|
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 | 191k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) |
1178 | 191k | { |
1179 | 191k | m_cost.InitializeBegin(); |
1180 | 191k | m_transaction_idxs = depgraph.Positions(); |
1181 | 191k | auto num_transactions = m_transaction_idxs.Count(); |
1182 | 191k | m_tx_data.resize(depgraph.PositionRange()); |
1183 | 191k | m_set_info.resize(num_transactions); |
1184 | 191k | m_reachable.resize(num_transactions); |
1185 | 191k | size_t num_chunks = 0; |
1186 | 191k | size_t num_deps = 0; |
1187 | 5.07M | for (auto tx_idx : m_transaction_idxs) { |
1188 | | // Fill in transaction data. |
1189 | 5.07M | auto& tx_data = m_tx_data[tx_idx]; |
1190 | 5.07M | tx_data.parents = depgraph.GetReducedParents(tx_idx); |
1191 | 15.0M | for (auto parent_idx : tx_data.parents) { |
1192 | 15.0M | m_tx_data[parent_idx].children.Set(tx_idx); |
1193 | 15.0M | } |
1194 | 5.07M | num_deps += tx_data.parents.Count(); |
1195 | | // Create a singleton chunk for it. |
1196 | 5.07M | tx_data.chunk_idx = num_chunks; |
1197 | 5.07M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); |
1198 | 5.07M | } |
1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. |
1200 | 5.26M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { |
1201 | 5.07M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; |
1202 | 5.07M | m_reachable[chunk_idx].first = tx_data.parents; |
1203 | 5.07M | m_reachable[chunk_idx].second = tx_data.children; |
1204 | 5.07M | } |
1205 | 191k | Assume(num_chunks == num_transactions); |
1206 | | // Mark all chunk sets as chunks. |
1207 | 191k | m_chunk_idxs = SetType::Fill(num_chunks); |
1208 | 191k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); |
1209 | 191k | } 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 | 50.6k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 50.6k | { | 1179 | 50.6k | m_cost.InitializeBegin(); | 1180 | 50.6k | m_transaction_idxs = depgraph.Positions(); | 1181 | 50.6k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 50.6k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 50.6k | m_set_info.resize(num_transactions); | 1184 | 50.6k | m_reachable.resize(num_transactions); | 1185 | 50.6k | size_t num_chunks = 0; | 1186 | 50.6k | size_t num_deps = 0; | 1187 | 1.45M | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 1.45M | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 1.45M | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 4.49M | for (auto parent_idx : tx_data.parents) { | 1192 | 4.49M | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 4.49M | } | 1194 | 1.45M | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 1.45M | tx_data.chunk_idx = num_chunks; | 1197 | 1.45M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 1.45M | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 1.50M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { | 1201 | 1.45M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 1.45M | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 1.45M | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 1.45M | } | 1205 | 50.6k | Assume(num_chunks == num_transactions); | 1206 | | // Mark all chunk sets as chunks. | 1207 | 50.6k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 50.6k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 50.6k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) Line | Count | Source | 1177 | 45.4k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 45.4k | { | 1179 | 45.4k | m_cost.InitializeBegin(); | 1180 | 45.4k | m_transaction_idxs = depgraph.Positions(); | 1181 | 45.4k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 45.4k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 45.4k | m_set_info.resize(num_transactions); | 1184 | 45.4k | m_reachable.resize(num_transactions); | 1185 | 45.4k | size_t num_chunks = 0; | 1186 | 45.4k | size_t num_deps = 0; | 1187 | 1.38M | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 1.38M | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 1.38M | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 4.42M | for (auto parent_idx : tx_data.parents) { | 1192 | 4.42M | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 4.42M | } | 1194 | 1.38M | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 1.38M | tx_data.chunk_idx = num_chunks; | 1197 | 1.38M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 1.38M | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 1.43M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { | 1201 | 1.38M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 1.38M | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 1.38M | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 1.38M | } | 1205 | 45.4k | Assume(num_chunks == num_transactions); | 1206 | | // Mark all chunk sets as chunks. | 1207 | 45.4k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 45.4k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 45.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) Line | Count | Source | 1177 | 45.4k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 45.4k | { | 1179 | 45.4k | m_cost.InitializeBegin(); | 1180 | 45.4k | m_transaction_idxs = depgraph.Positions(); | 1181 | 45.4k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 45.4k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 45.4k | m_set_info.resize(num_transactions); | 1184 | 45.4k | m_reachable.resize(num_transactions); | 1185 | 45.4k | size_t num_chunks = 0; | 1186 | 45.4k | size_t num_deps = 0; | 1187 | 1.38M | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 1.38M | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 1.38M | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 4.42M | for (auto parent_idx : tx_data.parents) { | 1192 | 4.42M | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 4.42M | } | 1194 | 1.38M | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 1.38M | tx_data.chunk_idx = num_chunks; | 1197 | 1.38M | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 1.38M | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 1.43M | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { | 1201 | 1.38M | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 1.38M | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 1.38M | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 1.38M | } | 1205 | 45.4k | Assume(num_chunks == num_transactions); | 1206 | | // Mark all chunk sets as chunks. | 1207 | 45.4k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 45.4k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 45.4k | } |
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&) Line | Count | Source | 1177 | 25.0k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 25.0k | { | 1179 | 25.0k | m_cost.InitializeBegin(); | 1180 | 25.0k | m_transaction_idxs = depgraph.Positions(); | 1181 | 25.0k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 25.0k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 25.0k | m_set_info.resize(num_transactions); | 1184 | 25.0k | m_reachable.resize(num_transactions); | 1185 | 25.0k | size_t num_chunks = 0; | 1186 | 25.0k | size_t num_deps = 0; | 1187 | 421k | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 421k | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 421k | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 844k | for (auto parent_idx : tx_data.parents) { | 1192 | 844k | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 844k | } | 1194 | 421k | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 421k | tx_data.chunk_idx = num_chunks; | 1197 | 421k | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 421k | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 446k | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { | 1201 | 421k | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 421k | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 421k | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 421k | } | 1205 | 25.0k | Assume(num_chunks == num_transactions); | 1206 | | // Mark all chunk sets as chunks. | 1207 | 25.0k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 25.0k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 25.0k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::SpanningForestState(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&, unsigned long, cluster_linearize::SFLDefaultCostModel const&) Line | Count | Source | 1177 | 25.0k | m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost) | 1178 | 25.0k | { | 1179 | 25.0k | m_cost.InitializeBegin(); | 1180 | 25.0k | m_transaction_idxs = depgraph.Positions(); | 1181 | 25.0k | auto num_transactions = m_transaction_idxs.Count(); | 1182 | 25.0k | m_tx_data.resize(depgraph.PositionRange()); | 1183 | 25.0k | m_set_info.resize(num_transactions); | 1184 | 25.0k | m_reachable.resize(num_transactions); | 1185 | 25.0k | size_t num_chunks = 0; | 1186 | 25.0k | size_t num_deps = 0; | 1187 | 421k | for (auto tx_idx : m_transaction_idxs) { | 1188 | | // Fill in transaction data. | 1189 | 421k | auto& tx_data = m_tx_data[tx_idx]; | 1190 | 421k | tx_data.parents = depgraph.GetReducedParents(tx_idx); | 1191 | 844k | for (auto parent_idx : tx_data.parents) { | 1192 | 844k | m_tx_data[parent_idx].children.Set(tx_idx); | 1193 | 844k | } | 1194 | 421k | num_deps += tx_data.parents.Count(); | 1195 | | // Create a singleton chunk for it. | 1196 | 421k | tx_data.chunk_idx = num_chunks; | 1197 | 421k | m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx); | 1198 | 421k | } | 1199 | | // Set the reachable transactions for each chunk to the transactions' parents and children. | 1200 | 446k | for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) { | 1201 | 421k | auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()]; | 1202 | 421k | m_reachable[chunk_idx].first = tx_data.parents; | 1203 | 421k | m_reachable[chunk_idx].second = tx_data.children; | 1204 | 421k | } | 1205 | 25.0k | Assume(num_chunks == num_transactions); | 1206 | | // Mark all chunk sets as chunks. | 1207 | 25.0k | m_chunk_idxs = SetType::Fill(num_chunks); | 1208 | 25.0k | m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps); | 1209 | 25.0k | } |
|
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 | 143k | { |
1216 | | // Add transactions one by one, in order of existing linearization. |
1217 | 3.79M | for (DepGraphIndex tx_idx : old_linearization) { |
1218 | 3.79M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; |
1219 | | // Merge the chunk upwards, as long as merging succeeds. |
1220 | 6.55M | while (true) { |
1221 | 6.55M | chunk_idx = MergeStep<false>(chunk_idx); |
1222 | 6.55M | if (chunk_idx == INVALID_SET_IDX) break; |
1223 | 6.55M | } |
1224 | 3.79M | } |
1225 | 143k | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 38.9k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 1.09M | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 1.09M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 1.89M | while (true) { | 1221 | 1.89M | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 1.89M | if (chunk_idx == INVALID_SET_IDX) break; | 1223 | 1.89M | } | 1224 | 1.09M | } | 1225 | 38.9k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 33.7k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 1.03M | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 1.03M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 1.77M | while (true) { | 1221 | 1.77M | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 1.77M | if (chunk_idx == INVALID_SET_IDX) break; | 1223 | 1.77M | } | 1224 | 1.03M | } | 1225 | 33.7k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 33.8k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 1.03M | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 1.03M | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 1.77M | while (true) { | 1221 | 1.77M | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 1.77M | if (chunk_idx == INVALID_SET_IDX) break; | 1223 | 1.77M | } | 1224 | 1.03M | } | 1225 | 33.8k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 18.7k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 315k | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 315k | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 553k | while (true) { | 1221 | 553k | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 553k | if (chunk_idx == INVALID_SET_IDX) break; | 1223 | 553k | } | 1224 | 315k | } | 1225 | 18.7k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::LoadLinearization(std::span<unsigned int const, 18446744073709551615ul>) Line | Count | Source | 1215 | 18.6k | { | 1216 | | // Add transactions one by one, in order of existing linearization. | 1217 | 314k | for (DepGraphIndex tx_idx : old_linearization) { | 1218 | 314k | auto chunk_idx = m_tx_data[tx_idx].chunk_idx; | 1219 | | // Merge the chunk upwards, as long as merging succeeds. | 1220 | 553k | while (true) { | 1221 | 553k | chunk_idx = MergeStep<false>(chunk_idx); | 1222 | 553k | if (chunk_idx == INVALID_SET_IDX) break; | 1223 | 553k | } | 1224 | 314k | } | 1225 | 18.6k | } |
|
1226 | | |
1227 | | /** Make state topological. Can be called after constructing, or after LoadLinearization. */ |
1228 | | void MakeTopological() noexcept |
1229 | 98.8k | { |
1230 | 98.8k | m_cost.MakeTopologicalBegin(); |
1231 | 98.8k | Assume(m_suboptimal_chunks.empty()); |
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 | 98.8k | unsigned init_dir = m_rng.randbool(); |
1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both |
1239 | | * directions. */ |
1240 | 98.8k | SetType merged_chunks; |
1241 | | // Mark chunks as suboptimal. |
1242 | 98.8k | m_suboptimal_idxs = m_chunk_idxs; |
1243 | 1.61M | for (auto chunk_idx : m_chunk_idxs) { |
1244 | 1.61M | m_suboptimal_chunks.emplace_back(chunk_idx); |
1245 | | // Randomize the initial order of suboptimal chunks in the queue. |
1246 | 1.61M | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); |
1247 | 1.61M | if (j != m_suboptimal_chunks.size() - 1) { |
1248 | 1.33M | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); |
1249 | 1.33M | } |
1250 | 1.61M | } |
1251 | 98.8k | unsigned chunks = m_chunk_idxs.Count(); |
1252 | 98.8k | unsigned steps = 0; |
1253 | 2.42M | while (!m_suboptimal_chunks.empty()) { |
1254 | 2.32M | ++steps; |
1255 | | // Pop an entry from the potentially-suboptimal chunk queue. |
1256 | 2.32M | SetIdx chunk_idx = m_suboptimal_chunks.front(); |
1257 | 2.32M | m_suboptimal_chunks.pop_front(); |
1258 | 2.32M | Assume(m_suboptimal_idxs[chunk_idx]); |
1259 | 2.32M | 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 | 2.32M | if (!m_chunk_idxs[chunk_idx]) continue; |
1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ |
1264 | 1.91M | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; |
1265 | 1.91M | int flip = m_rng.randbool(); |
1266 | 4.22M | for (int i = 0; i < 2; ++i) { |
1267 | 3.28M | if (i ^ flip) { |
1268 | 1.65M | if (!(direction & 1)) continue; |
1269 | | // Attempt to merge the chunk upwards. |
1270 | 1.06M | auto result_up = MergeStep<false>(chunk_idx); |
1271 | 1.06M | if (result_up != INVALID_SET_IDX) { |
1272 | 509k | if (!m_suboptimal_idxs[result_up]) { |
1273 | 509k | m_suboptimal_idxs.Set(result_up); |
1274 | 509k | m_suboptimal_chunks.push_back(result_up); |
1275 | 509k | } |
1276 | 509k | merged_chunks.Set(result_up); |
1277 | 509k | break; |
1278 | 509k | } |
1279 | 1.62M | } else { |
1280 | 1.62M | if (!(direction & 2)) continue; |
1281 | | // Attempt to merge the chunk downwards. |
1282 | 1.09M | auto result_down = MergeStep<true>(chunk_idx); |
1283 | 1.09M | if (result_down != INVALID_SET_IDX) { |
1284 | 460k | if (!m_suboptimal_idxs[result_down]) { |
1285 | 208k | m_suboptimal_idxs.Set(result_down); |
1286 | 208k | m_suboptimal_chunks.push_back(result_down); |
1287 | 208k | } |
1288 | 460k | merged_chunks.Set(result_down); |
1289 | 460k | break; |
1290 | 460k | } |
1291 | 1.09M | } |
1292 | 3.28M | } |
1293 | 1.91M | } |
1294 | 98.8k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); |
1295 | 98.8k | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 28.0k | { | 1230 | 28.0k | m_cost.MakeTopologicalBegin(); | 1231 | 28.0k | Assume(m_suboptimal_chunks.empty()); | 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 | 28.0k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 28.0k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 28.0k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 457k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 457k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 457k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 457k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 382k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 382k | } | 1250 | 457k | } | 1251 | 28.0k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 28.0k | unsigned steps = 0; | 1253 | 685k | while (!m_suboptimal_chunks.empty()) { | 1254 | 657k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 657k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 657k | m_suboptimal_chunks.pop_front(); | 1258 | 657k | Assume(m_suboptimal_idxs[chunk_idx]); | 1259 | 657k | 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 | 657k | if (!m_chunk_idxs[chunk_idx]) continue; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 541k | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; | 1265 | 541k | int flip = m_rng.randbool(); | 1266 | 1.20M | for (int i = 0; i < 2; ++i) { | 1267 | 932k | if (i ^ flip) { | 1268 | 469k | if (!(direction & 1)) continue; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 301k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 301k | if (result_up != INVALID_SET_IDX) { | 1272 | 140k | if (!m_suboptimal_idxs[result_up]) { | 1273 | 140k | m_suboptimal_idxs.Set(result_up); | 1274 | 140k | m_suboptimal_chunks.push_back(result_up); | 1275 | 140k | } | 1276 | 140k | merged_chunks.Set(result_up); | 1277 | 140k | break; | 1278 | 140k | } | 1279 | 463k | } else { | 1280 | 463k | if (!(direction & 2)) continue; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 310k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 310k | if (result_down != INVALID_SET_IDX) { | 1284 | 129k | if (!m_suboptimal_idxs[result_down]) { | 1285 | 59.2k | m_suboptimal_idxs.Set(result_down); | 1286 | 59.2k | m_suboptimal_chunks.push_back(result_down); | 1287 | 59.2k | } | 1288 | 129k | merged_chunks.Set(result_down); | 1289 | 129k | break; | 1290 | 129k | } | 1291 | 310k | } | 1292 | 932k | } | 1293 | 541k | } | 1294 | 28.0k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 28.0k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 22.8k | { | 1230 | 22.8k | m_cost.MakeTopologicalBegin(); | 1231 | 22.8k | Assume(m_suboptimal_chunks.empty()); | 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 | 22.8k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 22.8k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 22.8k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 447k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 447k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 447k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 447k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 378k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 378k | } | 1250 | 447k | } | 1251 | 22.8k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 22.8k | unsigned steps = 0; | 1253 | 671k | while (!m_suboptimal_chunks.empty()) { | 1254 | 648k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 648k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 648k | m_suboptimal_chunks.pop_front(); | 1258 | 648k | Assume(m_suboptimal_idxs[chunk_idx]); | 1259 | 648k | 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 | 648k | if (!m_chunk_idxs[chunk_idx]) continue; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 530k | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; | 1265 | 530k | int flip = m_rng.randbool(); | 1266 | 1.17M | for (int i = 0; i < 2; ++i) { | 1267 | 912k | if (i ^ flip) { | 1268 | 460k | if (!(direction & 1)) continue; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 300k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 300k | if (result_up != INVALID_SET_IDX) { | 1272 | 143k | if (!m_suboptimal_idxs[result_up]) { | 1273 | 143k | m_suboptimal_idxs.Set(result_up); | 1274 | 143k | m_suboptimal_chunks.push_back(result_up); | 1275 | 143k | } | 1276 | 143k | merged_chunks.Set(result_up); | 1277 | 143k | break; | 1278 | 143k | } | 1279 | 451k | } else { | 1280 | 451k | if (!(direction & 2)) continue; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 300k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 300k | if (result_down != INVALID_SET_IDX) { | 1284 | 126k | if (!m_suboptimal_idxs[result_down]) { | 1285 | 58.0k | m_suboptimal_idxs.Set(result_down); | 1286 | 58.0k | m_suboptimal_chunks.push_back(result_down); | 1287 | 58.0k | } | 1288 | 126k | merged_chunks.Set(result_down); | 1289 | 126k | break; | 1290 | 126k | } | 1291 | 300k | } | 1292 | 912k | } | 1293 | 530k | } | 1294 | 22.8k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 22.8k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 22.8k | { | 1230 | 22.8k | m_cost.MakeTopologicalBegin(); | 1231 | 22.8k | Assume(m_suboptimal_chunks.empty()); | 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 | 22.8k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 22.8k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 22.8k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 445k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 445k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 445k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 445k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 376k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 376k | } | 1250 | 445k | } | 1251 | 22.8k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 22.8k | unsigned steps = 0; | 1253 | 665k | while (!m_suboptimal_chunks.empty()) { | 1254 | 642k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 642k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 642k | m_suboptimal_chunks.pop_front(); | 1258 | 642k | Assume(m_suboptimal_idxs[chunk_idx]); | 1259 | 642k | 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 | 642k | if (!m_chunk_idxs[chunk_idx]) continue; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 527k | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; | 1265 | 527k | int flip = m_rng.randbool(); | 1266 | 1.16M | for (int i = 0; i < 2; ++i) { | 1267 | 907k | if (i ^ flip) { | 1268 | 457k | if (!(direction & 1)) continue; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 293k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 293k | if (result_up != INVALID_SET_IDX) { | 1272 | 140k | if (!m_suboptimal_idxs[result_up]) { | 1273 | 140k | m_suboptimal_idxs.Set(result_up); | 1274 | 140k | m_suboptimal_chunks.push_back(result_up); | 1275 | 140k | } | 1276 | 140k | merged_chunks.Set(result_up); | 1277 | 140k | break; | 1278 | 140k | } | 1279 | 450k | } else { | 1280 | 450k | if (!(direction & 2)) continue; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 303k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 303k | if (result_down != INVALID_SET_IDX) { | 1284 | 126k | if (!m_suboptimal_idxs[result_down]) { | 1285 | 56.8k | m_suboptimal_idxs.Set(result_down); | 1286 | 56.8k | m_suboptimal_chunks.push_back(result_down); | 1287 | 56.8k | } | 1288 | 126k | merged_chunks.Set(result_down); | 1289 | 126k | break; | 1290 | 126k | } | 1291 | 303k | } | 1292 | 907k | } | 1293 | 527k | } | 1294 | 22.8k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 22.8k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 12.5k | { | 1230 | 12.5k | m_cost.MakeTopologicalBegin(); | 1231 | 12.5k | Assume(m_suboptimal_chunks.empty()); | 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 | 12.5k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 12.5k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 12.5k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 130k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 130k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 130k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 130k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 99.5k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 99.5k | } | 1250 | 130k | } | 1251 | 12.5k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 12.5k | unsigned steps = 0; | 1253 | 202k | while (!m_suboptimal_chunks.empty()) { | 1254 | 190k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 190k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 190k | m_suboptimal_chunks.pop_front(); | 1258 | 190k | Assume(m_suboptimal_idxs[chunk_idx]); | 1259 | 190k | 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 | 190k | if (!m_chunk_idxs[chunk_idx]) continue; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 155k | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; | 1265 | 155k | int flip = m_rng.randbool(); | 1266 | 338k | for (int i = 0; i < 2; ++i) { | 1267 | 265k | if (i ^ flip) { | 1268 | 133k | if (!(direction & 1)) continue; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 86.8k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 86.8k | if (result_up != INVALID_SET_IDX) { | 1272 | 42.4k | if (!m_suboptimal_idxs[result_up]) { | 1273 | 42.4k | m_suboptimal_idxs.Set(result_up); | 1274 | 42.4k | m_suboptimal_chunks.push_back(result_up); | 1275 | 42.4k | } | 1276 | 42.4k | merged_chunks.Set(result_up); | 1277 | 42.4k | break; | 1278 | 42.4k | } | 1279 | 131k | } else { | 1280 | 131k | if (!(direction & 2)) continue; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 89.8k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 89.8k | if (result_down != INVALID_SET_IDX) { | 1284 | 39.2k | if (!m_suboptimal_idxs[result_down]) { | 1285 | 16.8k | m_suboptimal_idxs.Set(result_down); | 1286 | 16.8k | m_suboptimal_chunks.push_back(result_down); | 1287 | 16.8k | } | 1288 | 39.2k | merged_chunks.Set(result_down); | 1289 | 39.2k | break; | 1290 | 39.2k | } | 1291 | 89.8k | } | 1292 | 265k | } | 1293 | 155k | } | 1294 | 12.5k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 12.5k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MakeTopological() Line | Count | Source | 1229 | 12.5k | { | 1230 | 12.5k | m_cost.MakeTopologicalBegin(); | 1231 | 12.5k | Assume(m_suboptimal_chunks.empty()); | 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 | 12.5k | unsigned init_dir = m_rng.randbool(); | 1238 | | /** Which chunks are the result of merging, and thus need merge attempts in both | 1239 | | * directions. */ | 1240 | 12.5k | SetType merged_chunks; | 1241 | | // Mark chunks as suboptimal. | 1242 | 12.5k | m_suboptimal_idxs = m_chunk_idxs; | 1243 | 131k | for (auto chunk_idx : m_chunk_idxs) { | 1244 | 131k | m_suboptimal_chunks.emplace_back(chunk_idx); | 1245 | | // Randomize the initial order of suboptimal chunks in the queue. | 1246 | 131k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1247 | 131k | if (j != m_suboptimal_chunks.size() - 1) { | 1248 | 99.7k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1249 | 99.7k | } | 1250 | 131k | } | 1251 | 12.5k | unsigned chunks = m_chunk_idxs.Count(); | 1252 | 12.5k | unsigned steps = 0; | 1253 | 203k | while (!m_suboptimal_chunks.empty()) { | 1254 | 190k | ++steps; | 1255 | | // Pop an entry from the potentially-suboptimal chunk queue. | 1256 | 190k | SetIdx chunk_idx = m_suboptimal_chunks.front(); | 1257 | 190k | m_suboptimal_chunks.pop_front(); | 1258 | 190k | Assume(m_suboptimal_idxs[chunk_idx]); | 1259 | 190k | 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 | 190k | if (!m_chunk_idxs[chunk_idx]) continue; | 1263 | | /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */ | 1264 | 155k | unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1; | 1265 | 155k | int flip = m_rng.randbool(); | 1266 | 340k | for (int i = 0; i < 2; ++i) { | 1267 | 266k | if (i ^ flip) { | 1268 | 134k | if (!(direction & 1)) continue; | 1269 | | // Attempt to merge the chunk upwards. | 1270 | 87.7k | auto result_up = MergeStep<false>(chunk_idx); | 1271 | 87.7k | if (result_up != INVALID_SET_IDX) { | 1272 | 42.5k | if (!m_suboptimal_idxs[result_up]) { | 1273 | 42.5k | m_suboptimal_idxs.Set(result_up); | 1274 | 42.5k | m_suboptimal_chunks.push_back(result_up); | 1275 | 42.5k | } | 1276 | 42.5k | merged_chunks.Set(result_up); | 1277 | 42.5k | break; | 1278 | 42.5k | } | 1279 | 132k | } else { | 1280 | 132k | if (!(direction & 2)) continue; | 1281 | | // Attempt to merge the chunk downwards. | 1282 | 89.4k | auto result_down = MergeStep<true>(chunk_idx); | 1283 | 89.4k | if (result_down != INVALID_SET_IDX) { | 1284 | 38.8k | if (!m_suboptimal_idxs[result_down]) { | 1285 | 17.1k | m_suboptimal_idxs.Set(result_down); | 1286 | 17.1k | m_suboptimal_chunks.push_back(result_down); | 1287 | 17.1k | } | 1288 | 38.8k | merged_chunks.Set(result_down); | 1289 | 38.8k | break; | 1290 | 38.8k | } | 1291 | 89.4k | } | 1292 | 266k | } | 1293 | 155k | } | 1294 | 12.5k | m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps); | 1295 | 12.5k | } |
|
1296 | | |
1297 | | /** Initialize the data structure for optimization. It must be topological already. */ |
1298 | | void StartOptimizing() noexcept |
1299 | 191k | { |
1300 | 191k | m_cost.StartOptimizingBegin(); |
1301 | 191k | Assume(m_suboptimal_chunks.empty()); |
1302 | | // Mark chunks suboptimal. |
1303 | 191k | m_suboptimal_idxs = m_chunk_idxs; |
1304 | 1.33M | for (auto chunk_idx : m_chunk_idxs) { |
1305 | 1.33M | m_suboptimal_chunks.push_back(chunk_idx); |
1306 | | // Randomize the initial order of suboptimal chunks in the queue. |
1307 | 1.33M | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); |
1308 | 1.33M | if (j != m_suboptimal_chunks.size() - 1) { |
1309 | 948k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); |
1310 | 948k | } |
1311 | 1.33M | } |
1312 | 191k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); |
1313 | 191k | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 50.6k | { | 1300 | 50.6k | m_cost.StartOptimizingBegin(); | 1301 | 50.6k | Assume(m_suboptimal_chunks.empty()); | 1302 | | // Mark chunks suboptimal. | 1303 | 50.6k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 384k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 384k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 384k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 384k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 279k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 279k | } | 1311 | 384k | } | 1312 | 50.6k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 50.6k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 45.4k | { | 1300 | 45.4k | m_cost.StartOptimizingBegin(); | 1301 | 45.4k | Assume(m_suboptimal_chunks.empty()); | 1302 | | // Mark chunks suboptimal. | 1303 | 45.4k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 374k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 374k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 374k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 374k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 275k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 275k | } | 1311 | 374k | } | 1312 | 45.4k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 45.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 45.4k | { | 1300 | 45.4k | m_cost.StartOptimizingBegin(); | 1301 | 45.4k | Assume(m_suboptimal_chunks.empty()); | 1302 | | // Mark chunks suboptimal. | 1303 | 45.4k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 373k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 373k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 373k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 373k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 274k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 274k | } | 1311 | 373k | } | 1312 | 45.4k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 45.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 25.0k | { | 1300 | 25.0k | m_cost.StartOptimizingBegin(); | 1301 | 25.0k | Assume(m_suboptimal_chunks.empty()); | 1302 | | // Mark chunks suboptimal. | 1303 | 25.0k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 100k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 100k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 100k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 100k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 59.3k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 59.3k | } | 1311 | 100k | } | 1312 | 25.0k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 25.0k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::StartOptimizing() Line | Count | Source | 1299 | 25.0k | { | 1300 | 25.0k | m_cost.StartOptimizingBegin(); | 1301 | 25.0k | Assume(m_suboptimal_chunks.empty()); | 1302 | | // Mark chunks suboptimal. | 1303 | 25.0k | m_suboptimal_idxs = m_chunk_idxs; | 1304 | 100k | for (auto chunk_idx : m_chunk_idxs) { | 1305 | 100k | m_suboptimal_chunks.push_back(chunk_idx); | 1306 | | // Randomize the initial order of suboptimal chunks in the queue. | 1307 | 100k | SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size()); | 1308 | 100k | if (j != m_suboptimal_chunks.size() - 1) { | 1309 | 59.1k | std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); | 1310 | 59.1k | } | 1311 | 100k | } | 1312 | 25.0k | m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size()); | 1313 | 25.0k | } |
|
1314 | | |
1315 | | /** Try to improve the forest. Returns false if it is optimal, true otherwise. */ |
1316 | | bool OptimizeStep() noexcept |
1317 | 2.55M | { |
1318 | 2.55M | auto chunk_idx = PickChunkToOptimize(); |
1319 | 2.55M | if (chunk_idx == INVALID_SET_IDX) { |
1320 | | // No improvable chunk was found, we are done. |
1321 | 0 | return false; |
1322 | 0 | } |
1323 | 2.55M | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); |
1324 | 2.55M | if (parent_idx == TxIdx(-1)) { |
1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. |
1326 | 1.53M | return !m_suboptimal_chunks.empty(); |
1327 | 1.53M | } |
1328 | | // Deactivate the found dependency and then make the state topological again with a |
1329 | | // sequence of merges. |
1330 | 1.02M | Improve(parent_idx, child_idx); |
1331 | 1.02M | return true; |
1332 | 2.55M | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 744k | { | 1318 | 744k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 744k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 744k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 744k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 447k | return !m_suboptimal_chunks.empty(); | 1327 | 447k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 297k | Improve(parent_idx, child_idx); | 1331 | 297k | return true; | 1332 | 744k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 733k | { | 1318 | 733k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 733k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 733k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 733k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 436k | return !m_suboptimal_chunks.empty(); | 1327 | 436k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 297k | Improve(parent_idx, child_idx); | 1331 | 297k | return true; | 1332 | 733k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 735k | { | 1318 | 735k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 735k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 735k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 735k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 436k | return !m_suboptimal_chunks.empty(); | 1327 | 436k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 298k | Improve(parent_idx, child_idx); | 1331 | 298k | return true; | 1332 | 735k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 171k | { | 1318 | 171k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 171k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 171k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 171k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 107k | return !m_suboptimal_chunks.empty(); | 1327 | 107k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 63.6k | Improve(parent_idx, child_idx); | 1331 | 63.6k | return true; | 1332 | 171k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::OptimizeStep() Line | Count | Source | 1317 | 172k | { | 1318 | 172k | auto chunk_idx = PickChunkToOptimize(); | 1319 | 172k | if (chunk_idx == INVALID_SET_IDX) { | 1320 | | // No improvable chunk was found, we are done. | 1321 | 0 | return false; | 1322 | 0 | } | 1323 | 172k | auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx); | 1324 | 172k | if (parent_idx == TxIdx(-1)) { | 1325 | | // Nothing to improve in chunk_idx. Need to continue with other chunks, if any. | 1326 | 107k | return !m_suboptimal_chunks.empty(); | 1327 | 107k | } | 1328 | | // Deactivate the found dependency and then make the state topological again with a | 1329 | | // sequence of merges. | 1330 | 64.9k | Improve(parent_idx, child_idx); | 1331 | 64.9k | return true; | 1332 | 172k | } |
|
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 | 191k | { |
1338 | 191k | m_cost.StartMinimizingBegin(); |
1339 | 191k | m_nonminimal_chunks.clear(); |
1340 | 191k | 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 | 1.50M | for (auto chunk_idx : m_chunk_idxs) { |
1344 | 1.50M | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); |
1345 | 1.50M | 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 | 1.50M | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); |
1348 | 1.50M | if (j != m_nonminimal_chunks.size() - 1) { |
1349 | 1.10M | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); |
1350 | 1.10M | } |
1351 | 1.50M | } |
1352 | 191k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); |
1353 | 191k | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 50.6k | { | 1338 | 50.6k | m_cost.StartMinimizingBegin(); | 1339 | 50.6k | m_nonminimal_chunks.clear(); | 1340 | 50.6k | 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 | 437k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 437k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 437k | 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 | 437k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 437k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 328k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 328k | } | 1351 | 437k | } | 1352 | 50.6k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 50.6k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 45.4k | { | 1338 | 45.4k | m_cost.StartMinimizingBegin(); | 1339 | 45.4k | m_nonminimal_chunks.clear(); | 1340 | 45.4k | 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 | 427k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 427k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 427k | 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 | 427k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 427k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 323k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 323k | } | 1351 | 427k | } | 1352 | 45.4k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 45.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 45.4k | { | 1338 | 45.4k | m_cost.StartMinimizingBegin(); | 1339 | 45.4k | m_nonminimal_chunks.clear(); | 1340 | 45.4k | 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 | 427k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 427k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 427k | 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 | 427k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 427k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 323k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 323k | } | 1351 | 427k | } | 1352 | 45.4k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 45.4k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 25.0k | { | 1338 | 25.0k | m_cost.StartMinimizingBegin(); | 1339 | 25.0k | m_nonminimal_chunks.clear(); | 1340 | 25.0k | 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 | 106k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 106k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 106k | 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 | 106k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 106k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 64.5k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 64.5k | } | 1351 | 106k | } | 1352 | 25.0k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 25.0k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::StartMinimizing() Line | Count | Source | 1337 | 25.0k | { | 1338 | 25.0k | m_cost.StartMinimizingBegin(); | 1339 | 25.0k | m_nonminimal_chunks.clear(); | 1340 | 25.0k | 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 | 106k | for (auto chunk_idx : m_chunk_idxs) { | 1344 | 106k | TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions); | 1345 | 106k | 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 | 106k | SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size()); | 1348 | 106k | if (j != m_nonminimal_chunks.size() - 1) { | 1349 | 64.4k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]); | 1350 | 64.4k | } | 1351 | 106k | } | 1352 | 25.0k | m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size()); | 1353 | 25.0k | } |
|
1354 | | |
1355 | | /** Try to reduce a chunk's size. Returns false if all chunks are minimal, true otherwise. */ |
1356 | | bool MinimizeStep() noexcept |
1357 | 2.32M | { |
1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. |
1359 | 2.32M | if (m_nonminimal_chunks.empty()) return false; |
1360 | 2.12M | m_cost.MinimizeStepBegin(); |
1361 | | // Pop an entry from the potentially-non-minimal chunk queue. |
1362 | 2.12M | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); |
1363 | 2.12M | m_nonminimal_chunks.pop_front(); |
1364 | 2.12M | auto& chunk_info = m_set_info[chunk_idx]; |
1365 | | /** Whether to move the pivot down rather than up. */ |
1366 | 2.12M | bool move_pivot_down = flags & 1; |
1367 | | /** Whether this is already the second stage. */ |
1368 | 2.12M | 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 | 2.12M | std::pair<TxIdx, TxIdx> candidate_dep; |
1373 | 2.12M | uint64_t candidate_tiebreak{0}; |
1374 | 2.12M | bool have_any = false; |
1375 | | // Iterate over all transactions. |
1376 | 8.45M | for (auto tx_idx : chunk_info.transactions) { |
1377 | 8.45M | const auto& tx_data = m_tx_data[tx_idx]; |
1378 | | // Iterate over all active child dependencies of the transaction. |
1379 | 8.45M | for (auto child_idx : tx_data.active_children) { |
1380 | 6.32M | 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.32M | if (dep_top_info.feerate << chunk_info.feerate) continue; |
1385 | 3.04M | have_any = true; |
1386 | | // Skip if this dependency does not have pivot in the right place. |
1387 | 3.04M | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; |
1388 | | // Remember this as our chosen dependency if it has a better tiebreak. |
1389 | 2.40M | uint64_t tiebreak = m_rng.rand64() | 1; |
1390 | 2.40M | if (tiebreak > candidate_tiebreak) { |
1391 | 621k | candidate_tiebreak = tiebreak; |
1392 | 621k | candidate_dep = {tx_idx, child_idx}; |
1393 | 621k | } |
1394 | 2.40M | } |
1395 | 8.45M | } |
1396 | 2.12M | 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 | 2.12M | if (!have_any) return true; |
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 | 338k | if (candidate_tiebreak == 0) { |
1402 | | // Switch to other direction, and to second phase. |
1403 | 51.1k | flags ^= 3; |
1404 | 51.1k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); |
1405 | 51.1k | return true; |
1406 | 51.1k | } |
1407 | | |
1408 | | // Otherwise, deactivate the dependency that was found. |
1409 | 286k | 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 | 286k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; |
1413 | 286k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; |
1414 | 286k | 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 | 731 | 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 | 731 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); |
1422 | 731 | m_cost.MinimizeStepEnd(/*split=*/false); |
1423 | 286k | } 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 | 286k | if (move_pivot_down) { |
1432 | 80.2k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); |
1433 | 80.2k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); |
1434 | 80.2k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); |
1435 | 206k | } else { |
1436 | 206k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); |
1437 | 206k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); |
1438 | 206k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); |
1439 | 206k | } |
1440 | 286k | if (m_rng.randbool()) { |
1441 | 143k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); |
1442 | 143k | } |
1443 | 286k | m_cost.MinimizeStepEnd(/*split=*/true); |
1444 | 286k | } |
1445 | 286k | return true; |
1446 | 338k | } cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 727k | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 727k | if (m_nonminimal_chunks.empty()) return false; | 1360 | 677k | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 677k | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 677k | m_nonminimal_chunks.pop_front(); | 1364 | 677k | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 677k | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 677k | 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 | 677k | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 677k | uint64_t candidate_tiebreak{0}; | 1374 | 677k | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 2.68M | for (auto tx_idx : chunk_info.transactions) { | 1377 | 2.68M | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 2.68M | for (auto child_idx : tx_data.active_children) { | 1380 | 2.00M | 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 | 2.00M | if (dep_top_info.feerate << chunk_info.feerate) continue; | 1385 | 1.09M | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 1.09M | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 827k | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 827k | if (tiebreak > candidate_tiebreak) { | 1391 | 221k | candidate_tiebreak = tiebreak; | 1392 | 221k | candidate_dep = {tx_idx, child_idx}; | 1393 | 221k | } | 1394 | 827k | } | 1395 | 2.68M | } | 1396 | 677k | 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 | 677k | if (!have_any) return true; | 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 | 131k | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 23.1k | flags ^= 3; | 1404 | 23.1k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 23.1k | return true; | 1406 | 23.1k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 108k | 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 | 108k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 108k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 108k | 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 | 237 | 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 | 237 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); | 1422 | 237 | m_cost.MinimizeStepEnd(/*split=*/false); | 1423 | 107k | } 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 | 107k | if (move_pivot_down) { | 1432 | 36.5k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 36.5k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 36.5k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 71.2k | } else { | 1436 | 71.2k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 71.2k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 71.2k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 71.2k | } | 1440 | 107k | if (m_rng.randbool()) { | 1441 | 54.2k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 54.2k | } | 1443 | 107k | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 107k | } | 1445 | 108k | return true; | 1446 | 131k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 596k | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 596k | if (m_nonminimal_chunks.empty()) return false; | 1360 | 550k | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 550k | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 550k | m_nonminimal_chunks.pop_front(); | 1364 | 550k | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 550k | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 550k | 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 | 550k | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 550k | uint64_t candidate_tiebreak{0}; | 1374 | 550k | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 2.17M | for (auto tx_idx : chunk_info.transactions) { | 1377 | 2.17M | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 2.17M | for (auto child_idx : tx_data.active_children) { | 1380 | 1.61M | 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 | 1.61M | if (dep_top_info.feerate << chunk_info.feerate) continue; | 1385 | 717k | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 717k | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 601k | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 601k | if (tiebreak > candidate_tiebreak) { | 1391 | 135k | candidate_tiebreak = tiebreak; | 1392 | 135k | candidate_dep = {tx_idx, child_idx}; | 1393 | 135k | } | 1394 | 601k | } | 1395 | 2.17M | } | 1396 | 550k | 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 | 550k | if (!have_any) return true; | 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 | 66.3k | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 8.64k | flags ^= 3; | 1404 | 8.64k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 8.64k | return true; | 1406 | 8.64k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 57.6k | 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 | 57.6k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 57.6k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 57.6k | 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 | 251 | 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 | 251 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); | 1422 | 251 | m_cost.MinimizeStepEnd(/*split=*/false); | 1423 | 57.4k | } 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 | 57.4k | if (move_pivot_down) { | 1432 | 13.0k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 13.0k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 13.0k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 44.3k | } else { | 1436 | 44.3k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 44.3k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 44.3k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 44.3k | } | 1440 | 57.4k | if (m_rng.randbool()) { | 1441 | 28.6k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 28.6k | } | 1443 | 57.4k | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 57.4k | } | 1445 | 57.6k | return true; | 1446 | 66.3k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 596k | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 596k | if (m_nonminimal_chunks.empty()) return false; | 1360 | 550k | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 550k | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 550k | m_nonminimal_chunks.pop_front(); | 1364 | 550k | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 550k | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 550k | 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 | 550k | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 550k | uint64_t candidate_tiebreak{0}; | 1374 | 550k | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 2.17M | for (auto tx_idx : chunk_info.transactions) { | 1377 | 2.17M | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 2.17M | for (auto child_idx : tx_data.active_children) { | 1380 | 1.62M | 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 | 1.62M | if (dep_top_info.feerate << chunk_info.feerate) continue; | 1385 | 718k | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 718k | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 601k | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 601k | if (tiebreak > candidate_tiebreak) { | 1391 | 135k | candidate_tiebreak = tiebreak; | 1392 | 135k | candidate_dep = {tx_idx, child_idx}; | 1393 | 135k | } | 1394 | 601k | } | 1395 | 2.17M | } | 1396 | 550k | 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 | 550k | if (!have_any) return true; | 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 | 66.3k | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 8.72k | flags ^= 3; | 1404 | 8.72k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 8.72k | return true; | 1406 | 8.72k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 57.6k | 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 | 57.6k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 57.6k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 57.6k | 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 | 243 | 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 | 243 | m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags); | 1422 | 243 | m_cost.MinimizeStepEnd(/*split=*/false); | 1423 | 57.4k | } 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 | 57.4k | if (move_pivot_down) { | 1432 | 13.2k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 13.2k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 13.2k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 44.1k | } else { | 1436 | 44.1k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 44.1k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 44.1k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 44.1k | } | 1440 | 57.4k | if (m_rng.randbool()) { | 1441 | 28.6k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 28.6k | } | 1443 | 57.4k | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 57.4k | } | 1445 | 57.6k | return true; | 1446 | 66.3k | } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 200k | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 200k | if (m_nonminimal_chunks.empty()) return false; | 1360 | 175k | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 175k | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 175k | m_nonminimal_chunks.pop_front(); | 1364 | 175k | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 175k | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 175k | 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 | 175k | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 175k | uint64_t candidate_tiebreak{0}; | 1374 | 175k | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 717k | for (auto tx_idx : chunk_info.transactions) { | 1377 | 717k | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 717k | for (auto child_idx : tx_data.active_children) { | 1380 | 542k | 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 | 542k | if (dep_top_info.feerate << chunk_info.feerate) continue; | 1385 | 259k | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 259k | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 188k | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 188k | if (tiebreak > candidate_tiebreak) { | 1391 | 64.8k | candidate_tiebreak = tiebreak; | 1392 | 64.8k | candidate_dep = {tx_idx, child_idx}; | 1393 | 64.8k | } | 1394 | 188k | } | 1395 | 717k | } | 1396 | 175k | 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 | 175k | if (!have_any) return true; | 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 | 37.1k | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 5.30k | flags ^= 3; | 1404 | 5.30k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 5.30k | return true; | 1406 | 5.30k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 31.8k | 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 | 31.8k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 31.8k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 31.8k | 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 | 31.8k | } 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 | 31.8k | if (move_pivot_down) { | 1432 | 8.65k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 8.65k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 8.65k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 23.1k | } else { | 1436 | 23.1k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 23.1k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 23.1k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 23.1k | } | 1440 | 31.8k | if (m_rng.randbool()) { | 1441 | 15.8k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 15.8k | } | 1443 | 31.8k | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 31.8k | } | 1445 | 31.8k | return true; | 1446 | 37.1k | } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::MinimizeStep() Line | Count | Source | 1357 | 200k | { | 1358 | | // If the queue of potentially-non-minimal chunks is empty, we are done. | 1359 | 200k | if (m_nonminimal_chunks.empty()) return false; | 1360 | 175k | m_cost.MinimizeStepBegin(); | 1361 | | // Pop an entry from the potentially-non-minimal chunk queue. | 1362 | 175k | auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front(); | 1363 | 175k | m_nonminimal_chunks.pop_front(); | 1364 | 175k | auto& chunk_info = m_set_info[chunk_idx]; | 1365 | | /** Whether to move the pivot down rather than up. */ | 1366 | 175k | bool move_pivot_down = flags & 1; | 1367 | | /** Whether this is already the second stage. */ | 1368 | 175k | 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 | 175k | std::pair<TxIdx, TxIdx> candidate_dep; | 1373 | 175k | uint64_t candidate_tiebreak{0}; | 1374 | 175k | bool have_any = false; | 1375 | | // Iterate over all transactions. | 1376 | 717k | for (auto tx_idx : chunk_info.transactions) { | 1377 | 717k | const auto& tx_data = m_tx_data[tx_idx]; | 1378 | | // Iterate over all active child dependencies of the transaction. | 1379 | 717k | for (auto child_idx : tx_data.active_children) { | 1380 | 541k | 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 | 541k | if (dep_top_info.feerate << chunk_info.feerate) continue; | 1385 | 258k | have_any = true; | 1386 | | // Skip if this dependency does not have pivot in the right place. | 1387 | 258k | if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue; | 1388 | | // Remember this as our chosen dependency if it has a better tiebreak. | 1389 | 189k | uint64_t tiebreak = m_rng.rand64() | 1; | 1390 | 189k | if (tiebreak > candidate_tiebreak) { | 1391 | 64.8k | candidate_tiebreak = tiebreak; | 1392 | 64.8k | candidate_dep = {tx_idx, child_idx}; | 1393 | 64.8k | } | 1394 | 189k | } | 1395 | 717k | } | 1396 | 175k | 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 | 175k | if (!have_any) return true; | 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 | 37.1k | if (candidate_tiebreak == 0) { | 1402 | | // Switch to other direction, and to second phase. | 1403 | 5.30k | flags ^= 3; | 1404 | 5.30k | if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags); | 1405 | 5.30k | return true; | 1406 | 5.30k | } | 1407 | | | 1408 | | // Otherwise, deactivate the dependency that was found. | 1409 | 31.8k | 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 | 31.8k | auto& parent_reachable = m_reachable[parent_chunk_idx].first; | 1413 | 31.8k | auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions; | 1414 | 31.8k | 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 | 31.8k | } 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 | 31.8k | if (move_pivot_down) { | 1432 | 8.63k | auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions); | 1433 | 8.63k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>()); | 1434 | 8.63k | m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags); | 1435 | 23.1k | } else { | 1436 | 23.1k | auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions); | 1437 | 23.1k | m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags); | 1438 | 23.1k | m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>()); | 1439 | 23.1k | } | 1440 | 31.8k | if (m_rng.randbool()) { | 1441 | 15.8k | std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]); | 1442 | 15.8k | } | 1443 | 31.8k | m_cost.MinimizeStepEnd(/*split=*/true); | 1444 | 31.8k | } | 1445 | 31.8k | return true; | 1446 | 37.1k | } |
|
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 | 191k | { |
1466 | 191k | m_cost.GetLinearizationBegin(); |
1467 | | /** The output linearization. */ |
1468 | 191k | std::vector<DepGraphIndex> ret; |
1469 | 191k | 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 | 191k | 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 | 191k | 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 | 191k | 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 | 191k | std::vector<TxIdx> ready_tx; |
1483 | | // Populate chunk_deps and tx_deps. |
1484 | 191k | unsigned num_deps{0}; |
1485 | 5.07M | for (TxIdx chl_idx : m_transaction_idxs) { |
1486 | 5.07M | const auto& chl_data = m_tx_data[chl_idx]; |
1487 | 5.07M | tx_deps[chl_idx] = chl_data.parents.Count(); |
1488 | 5.07M | num_deps += tx_deps[chl_idx]; |
1489 | 5.07M | auto chl_chunk_idx = chl_data.chunk_idx; |
1490 | 5.07M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; |
1491 | 5.07M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); |
1492 | 5.07M | } |
1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ |
1494 | 1.79M | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { |
1495 | 1.79M | auto& chunk = m_set_info[chunk_idx].transactions; |
1496 | 1.79M | auto it = chunk.begin(); |
1497 | 1.79M | DepGraphIndex ret = *it; |
1498 | 1.79M | ++it; |
1499 | 5.07M | while (it != chunk.end()) { |
1500 | 3.28M | if (fallback_order(*it, ret) > 0) ret = *it; |
1501 | 3.28M | ++it; |
1502 | 3.28M | } |
1503 | 1.79M | return ret; |
1504 | 1.79M | }; std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(unsigned char)::operator()(unsigned char) const Line | Count | Source | 1494 | 484k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 484k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 484k | auto it = chunk.begin(); | 1497 | 484k | DepGraphIndex ret = *it; | 1498 | 484k | ++it; | 1499 | 1.38M | while (it != chunk.end()) { | 1500 | 902k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 902k | ++it; | 1502 | 902k | } | 1503 | 484k | return ret; | 1504 | 484k | }; |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(unsigned char)::operator()(unsigned char) const Line | Count | Source | 1494 | 484k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 484k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 484k | auto it = chunk.begin(); | 1497 | 484k | DepGraphIndex ret = *it; | 1498 | 484k | ++it; | 1499 | 1.38M | while (it != chunk.end()) { | 1500 | 902k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 902k | ++it; | 1502 | 902k | } | 1503 | 484k | return ret; | 1504 | 484k | }; |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(unsigned char)::operator()(unsigned char) const Line | Count | Source | 1494 | 484k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 484k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 484k | auto it = chunk.begin(); | 1497 | 484k | DepGraphIndex ret = *it; | 1498 | 484k | ++it; | 1499 | 1.38M | while (it != chunk.end()) { | 1500 | 902k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 902k | ++it; | 1502 | 902k | } | 1503 | 484k | return ret; | 1504 | 484k | }; |
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 Line | Count | Source | 1494 | 138k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 138k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 138k | auto it = chunk.begin(); | 1497 | 138k | DepGraphIndex ret = *it; | 1498 | 138k | ++it; | 1499 | 421k | while (it != chunk.end()) { | 1500 | 282k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 282k | ++it; | 1502 | 282k | } | 1503 | 138k | return ret; | 1504 | 138k | }; |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&)::'lambda'(unsigned char)::operator()(unsigned char) const Line | Count | Source | 1494 | 138k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 138k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 138k | auto it = chunk.begin(); | 1497 | 138k | DepGraphIndex ret = *it; | 1498 | 138k | ++it; | 1499 | 421k | while (it != chunk.end()) { | 1500 | 282k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 282k | ++it; | 1502 | 282k | } | 1503 | 138k | return ret; | 1504 | 138k | }; |
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 | 61.4k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 61.4k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 61.4k | auto it = chunk.begin(); | 1497 | 61.4k | DepGraphIndex ret = *it; | 1498 | 61.4k | ++it; | 1499 | 68.8k | while (it != chunk.end()) { | 1500 | 7.46k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 7.46k | ++it; | 1502 | 7.46k | } | 1503 | 61.4k | return ret; | 1504 | 61.4k | }; |
|
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 | 8.58M | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { |
1508 | | // Bail out for identical transactions. |
1509 | 8.58M | if (a == b) return false; |
1510 | | // First sort by increasing transaction feerate. |
1511 | 8.58M | auto& a_feerate = m_depgraph.FeeRate(a); |
1512 | 8.58M | auto& b_feerate = m_depgraph.FeeRate(b); |
1513 | 8.58M | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); |
1514 | 8.58M | if (feerate_cmp != 0) return feerate_cmp < 0; |
1515 | | // Then by decreasing transaction size. |
1516 | 3.46M | if (a_feerate.size != b_feerate.size) { |
1517 | 3.40k | return a_feerate.size > b_feerate.size; |
1518 | 3.40k | } |
1519 | | // Tie-break by decreasing fallback_order. |
1520 | 3.46M | auto fallback_cmp = fallback_order(a, b); |
1521 | 3.46M | 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); |
1524 | 0 | return a < b; |
1525 | 3.46M | }; auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, 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 Line | Count | Source | 1507 | 2.41M | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 2.41M | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 2.41M | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 2.41M | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 2.41M | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 2.41M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 1.00M | if (a_feerate.size != b_feerate.size) { | 1517 | 1.00k | return a_feerate.size > b_feerate.size; | 1518 | 1.00k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 999k | auto fallback_cmp = fallback_order(a, b); | 1521 | 999k | 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); | 1524 | 0 | return a < b; | 1525 | 999k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, 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 Line | Count | Source | 1507 | 2.41M | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 2.41M | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 2.41M | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 2.41M | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 2.41M | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 2.41M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 1.00M | if (a_feerate.size != b_feerate.size) { | 1517 | 1.00k | return a_feerate.size > b_feerate.size; | 1518 | 1.00k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 999k | auto fallback_cmp = fallback_order(a, b); | 1521 | 999k | 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); | 1524 | 0 | return a < b; | 1525 | 999k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, 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 Line | Count | Source | 1507 | 2.41M | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 2.41M | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 2.41M | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 2.41M | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 2.41M | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 2.41M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 1.00M | if (a_feerate.size != b_feerate.size) { | 1517 | 1.00k | return a_feerate.size > b_feerate.size; | 1518 | 1.00k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 999k | auto fallback_cmp = fallback_order(a, b); | 1521 | 999k | 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); | 1524 | 0 | return a < b; | 1525 | 999k | }; |
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 Line | Count | Source | 1507 | 663k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 663k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 663k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 663k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 663k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 663k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 221k | if (a_feerate.size != b_feerate.size) { | 1517 | 200 | return a_feerate.size > b_feerate.size; | 1518 | 200 | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 221k | auto fallback_cmp = fallback_order(a, b); | 1521 | 221k | 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); | 1524 | 0 | return a < b; | 1525 | 221k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, 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 Line | Count | Source | 1507 | 663k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 663k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 663k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 663k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 663k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 663k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 221k | if (a_feerate.size != b_feerate.size) { | 1517 | 200 | return a_feerate.size > b_feerate.size; | 1518 | 200 | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 221k | auto fallback_cmp = fallback_order(a, b); | 1521 | 221k | 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); | 1524 | 0 | return a < b; | 1525 | 221k | }; |
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 Line | Count | Source | 1507 | 22.5k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 22.5k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 22.5k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 22.5k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 22.5k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 22.5k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 22.4k | 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 | 22.4k | auto fallback_cmp = fallback_order(a, b); | 1521 | 22.4k | 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); | 1524 | 0 | return a < b; | 1525 | 22.4k | }; |
|
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 | 5.23M | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { |
1530 | | // Bail out for identical chunks. |
1531 | 5.23M | if (a.first == b.first) return false; |
1532 | | // First sort by increasing chunk feerate. |
1533 | 5.23M | auto& chunk_feerate_a = m_set_info[a.first].feerate; |
1534 | 5.23M | auto& chunk_feerate_b = m_set_info[b.first].feerate; |
1535 | 5.23M | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); |
1536 | 5.23M | if (feerate_cmp != 0) return feerate_cmp < 0; |
1537 | | // Then by decreasing chunk size. |
1538 | 1.84M | if (chunk_feerate_a.size != chunk_feerate_b.size) { |
1539 | 69.0k | return chunk_feerate_a.size > chunk_feerate_b.size; |
1540 | 69.0k | } |
1541 | | // Tie-break by decreasing fallback_order. |
1542 | 1.77M | auto fallback_cmp = fallback_order(a.second, b.second); |
1543 | 1.77M | 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); |
1546 | 0 | return a.second < b.second; |
1547 | 1.77M | }; auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, 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 Line | Count | Source | 1529 | 1.53M | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 1.53M | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 1.53M | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 1.53M | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 1.53M | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 1.53M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 479k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 15.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 15.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 464k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 464k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 464k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, 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 Line | Count | Source | 1529 | 1.53M | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 1.53M | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 1.53M | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 1.53M | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 1.53M | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 1.53M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 479k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 15.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 15.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 464k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 464k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 464k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, 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 Line | Count | Source | 1529 | 1.53M | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 1.53M | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 1.53M | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 1.53M | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 1.53M | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 1.53M | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 479k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 15.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 15.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 464k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 464k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 464k | }; |
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 Line | Count | Source | 1529 | 291k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 291k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 291k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 291k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 291k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 291k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 180k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 12.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 12.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 168k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 168k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 168k | }; |
auto std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, 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 Line | Count | Source | 1529 | 291k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 291k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 291k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 291k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 291k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 291k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 180k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 12.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 12.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 168k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 168k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 168k | }; |
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 Line | Count | Source | 1529 | 42.9k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 42.9k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 42.9k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 42.9k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 42.9k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 42.9k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 42.2k | 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 | 42.2k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 42.2k | 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); | 1546 | 0 | return a.second < b.second; | 1547 | 42.2k | }; |
|
1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. |
1549 | 1.79M | for (SetIdx chunk_idx : m_chunk_idxs) { |
1550 | 1.79M | if (chunk_deps[chunk_idx] == 0) { |
1551 | 460k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); |
1552 | 460k | } |
1553 | 1.79M | } |
1554 | 191k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1555 | | // Pop chunks off the heap. |
1556 | 1.98M | while (!ready_chunks.empty()) { |
1557 | 1.79M | auto [chunk_idx, _rnd] = ready_chunks.front(); |
1558 | 1.79M | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1559 | 1.79M | ready_chunks.pop_back(); |
1560 | 1.79M | Assume(chunk_deps[chunk_idx] == 0); |
1561 | 1.79M | const auto& chunk_txn = m_set_info[chunk_idx].transactions; |
1562 | | // Build heap of all includable transactions in chunk. |
1563 | 1.79M | Assume(ready_tx.empty()); |
1564 | 5.07M | for (TxIdx tx_idx : chunk_txn) { |
1565 | 5.07M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); |
1566 | 5.07M | } |
1567 | 1.79M | Assume(!ready_tx.empty()); |
1568 | 1.79M | 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.86M | while (!ready_tx.empty()) { |
1572 | | // Pop an element from the tx_ready heap. |
1573 | 5.07M | auto tx_idx = ready_tx.front(); |
1574 | 5.07M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); |
1575 | 5.07M | ready_tx.pop_back(); |
1576 | | // Append to linearization. |
1577 | 5.07M | ret.push_back(tx_idx); |
1578 | | // Decrement dependency counts. |
1579 | 5.07M | auto& tx_data = m_tx_data[tx_idx]; |
1580 | 15.0M | for (TxIdx chl_idx : tx_data.children) { |
1581 | 15.0M | auto& chl_data = m_tx_data[chl_idx]; |
1582 | | // Decrement tx dependency count. |
1583 | 15.0M | Assume(tx_deps[chl_idx] > 0); |
1584 | 15.0M | 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 | 2.66M | ready_tx.push_back(chl_idx); |
1587 | 2.66M | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); |
1588 | 2.66M | } |
1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. |
1590 | 15.0M | if (chl_data.chunk_idx != chunk_idx) { |
1591 | 8.14M | Assume(chunk_deps[chl_data.chunk_idx] > 0); |
1592 | 8.14M | if (--chunk_deps[chl_data.chunk_idx] == 0) { |
1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. |
1594 | 1.33M | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); |
1595 | 1.33M | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); |
1596 | 1.33M | } |
1597 | 8.14M | } |
1598 | 15.0M | } |
1599 | 5.07M | } |
1600 | 1.79M | } |
1601 | 191k | Assume(ret.size() == m_set_info.size()); |
1602 | 191k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); |
1603 | 191k | return ret; |
1604 | 191k | } std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&) Line | Count | Source | 1465 | 45.4k | { | 1466 | 45.4k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 45.4k | std::vector<DepGraphIndex> ret; | 1469 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 45.4k | unsigned num_deps{0}; | 1485 | 1.38M | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 1.38M | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 1.38M | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 1.38M | num_deps += tx_deps[chl_idx]; | 1489 | 1.38M | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 1.38M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 1.38M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 1.38M | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 45.4k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 45.4k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 45.4k | auto it = chunk.begin(); | 1497 | 45.4k | DepGraphIndex ret = *it; | 1498 | 45.4k | ++it; | 1499 | 45.4k | while (it != chunk.end()) { | 1500 | 45.4k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 45.4k | ++it; | 1502 | 45.4k | } | 1503 | 45.4k | return ret; | 1504 | 45.4k | }; | 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 | 45.4k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 45.4k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 45.4k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 45.4k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 45.4k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 45.4k | if (a_feerate.size != b_feerate.size) { | 1517 | 45.4k | return a_feerate.size > b_feerate.size; | 1518 | 45.4k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 45.4k | auto fallback_cmp = fallback_order(a, b); | 1521 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 45.4k | Assume(false); | 1524 | 45.4k | return a < b; | 1525 | 45.4k | }; | 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 | 45.4k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 45.4k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 45.4k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 45.4k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 45.4k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 45.4k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 45.4k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 45.4k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 45.4k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 45.4k | Assume(false); | 1546 | 45.4k | return a.second < b.second; | 1547 | 45.4k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 484k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 484k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 128k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 128k | } | 1553 | 484k | } | 1554 | 45.4k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 529k | while (!ready_chunks.empty()) { | 1557 | 484k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 484k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 484k | ready_chunks.pop_back(); | 1560 | 484k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 484k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 484k | Assume(ready_tx.empty()); | 1564 | 1.38M | for (TxIdx tx_idx : chunk_txn) { | 1565 | 1.38M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 1.38M | } | 1567 | 484k | Assume(!ready_tx.empty()); | 1568 | 484k | 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 | 1.87M | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 1.38M | auto tx_idx = ready_tx.front(); | 1574 | 1.38M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 1.38M | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 1.38M | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 1.38M | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 4.42M | for (TxIdx chl_idx : tx_data.children) { | 1581 | 4.42M | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 4.42M | Assume(tx_deps[chl_idx] > 0); | 1584 | 4.42M | 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 | 736k | ready_tx.push_back(chl_idx); | 1587 | 736k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 736k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 4.42M | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 2.51M | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 2.51M | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 356k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 356k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 356k | } | 1597 | 2.51M | } | 1598 | 4.42M | } | 1599 | 1.38M | } | 1600 | 484k | } | 1601 | 45.4k | Assume(ret.size() == m_set_info.size()); | 1602 | 45.4k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 45.4k | return ret; | 1604 | 45.4k | } |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&) Line | Count | Source | 1465 | 45.4k | { | 1466 | 45.4k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 45.4k | std::vector<DepGraphIndex> ret; | 1469 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 45.4k | unsigned num_deps{0}; | 1485 | 1.38M | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 1.38M | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 1.38M | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 1.38M | num_deps += tx_deps[chl_idx]; | 1489 | 1.38M | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 1.38M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 1.38M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 1.38M | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 45.4k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 45.4k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 45.4k | auto it = chunk.begin(); | 1497 | 45.4k | DepGraphIndex ret = *it; | 1498 | 45.4k | ++it; | 1499 | 45.4k | while (it != chunk.end()) { | 1500 | 45.4k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 45.4k | ++it; | 1502 | 45.4k | } | 1503 | 45.4k | return ret; | 1504 | 45.4k | }; | 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 | 45.4k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 45.4k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 45.4k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 45.4k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 45.4k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 45.4k | if (a_feerate.size != b_feerate.size) { | 1517 | 45.4k | return a_feerate.size > b_feerate.size; | 1518 | 45.4k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 45.4k | auto fallback_cmp = fallback_order(a, b); | 1521 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 45.4k | Assume(false); | 1524 | 45.4k | return a < b; | 1525 | 45.4k | }; | 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 | 45.4k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 45.4k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 45.4k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 45.4k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 45.4k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 45.4k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 45.4k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 45.4k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 45.4k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 45.4k | Assume(false); | 1546 | 45.4k | return a.second < b.second; | 1547 | 45.4k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 484k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 484k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 128k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 128k | } | 1553 | 484k | } | 1554 | 45.4k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 529k | while (!ready_chunks.empty()) { | 1557 | 484k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 484k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 484k | ready_chunks.pop_back(); | 1560 | 484k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 484k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 484k | Assume(ready_tx.empty()); | 1564 | 1.38M | for (TxIdx tx_idx : chunk_txn) { | 1565 | 1.38M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 1.38M | } | 1567 | 484k | Assume(!ready_tx.empty()); | 1568 | 484k | 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 | 1.87M | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 1.38M | auto tx_idx = ready_tx.front(); | 1574 | 1.38M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 1.38M | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 1.38M | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 1.38M | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 4.42M | for (TxIdx chl_idx : tx_data.children) { | 1581 | 4.42M | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 4.42M | Assume(tx_deps[chl_idx] > 0); | 1584 | 4.42M | 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 | 736k | ready_tx.push_back(chl_idx); | 1587 | 736k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 736k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 4.42M | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 2.51M | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 2.51M | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 356k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 356k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 356k | } | 1597 | 2.51M | } | 1598 | 4.42M | } | 1599 | 1.38M | } | 1600 | 484k | } | 1601 | 45.4k | Assume(ret.size() == m_set_info.size()); | 1602 | 45.4k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 45.4k | return ret; | 1604 | 45.4k | } |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&) Line | Count | Source | 1465 | 45.4k | { | 1466 | 45.4k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 45.4k | std::vector<DepGraphIndex> ret; | 1469 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | 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 | 45.4k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 45.4k | unsigned num_deps{0}; | 1485 | 1.38M | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 1.38M | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 1.38M | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 1.38M | num_deps += tx_deps[chl_idx]; | 1489 | 1.38M | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 1.38M | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 1.38M | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 1.38M | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 45.4k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 45.4k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 45.4k | auto it = chunk.begin(); | 1497 | 45.4k | DepGraphIndex ret = *it; | 1498 | 45.4k | ++it; | 1499 | 45.4k | while (it != chunk.end()) { | 1500 | 45.4k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 45.4k | ++it; | 1502 | 45.4k | } | 1503 | 45.4k | return ret; | 1504 | 45.4k | }; | 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 | 45.4k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 45.4k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 45.4k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 45.4k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 45.4k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 45.4k | if (a_feerate.size != b_feerate.size) { | 1517 | 45.4k | return a_feerate.size > b_feerate.size; | 1518 | 45.4k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 45.4k | auto fallback_cmp = fallback_order(a, b); | 1521 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 45.4k | Assume(false); | 1524 | 45.4k | return a < b; | 1525 | 45.4k | }; | 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 | 45.4k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 45.4k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 45.4k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 45.4k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 45.4k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 45.4k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 45.4k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 45.4k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 45.4k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 45.4k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 45.4k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 45.4k | Assume(false); | 1546 | 45.4k | return a.second < b.second; | 1547 | 45.4k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 484k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 484k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 128k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 128k | } | 1553 | 484k | } | 1554 | 45.4k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 529k | while (!ready_chunks.empty()) { | 1557 | 484k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 484k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 484k | ready_chunks.pop_back(); | 1560 | 484k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 484k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 484k | Assume(ready_tx.empty()); | 1564 | 1.38M | for (TxIdx tx_idx : chunk_txn) { | 1565 | 1.38M | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 1.38M | } | 1567 | 484k | Assume(!ready_tx.empty()); | 1568 | 484k | 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 | 1.87M | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 1.38M | auto tx_idx = ready_tx.front(); | 1574 | 1.38M | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 1.38M | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 1.38M | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 1.38M | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 4.42M | for (TxIdx chl_idx : tx_data.children) { | 1581 | 4.42M | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 4.42M | Assume(tx_deps[chl_idx] > 0); | 1584 | 4.42M | 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 | 736k | ready_tx.push_back(chl_idx); | 1587 | 736k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 736k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 4.42M | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 2.51M | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 2.51M | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 356k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 356k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 356k | } | 1597 | 2.51M | } | 1598 | 4.42M | } | 1599 | 1.38M | } | 1600 | 484k | } | 1601 | 45.4k | Assume(ret.size() == m_set_info.size()); | 1602 | 45.4k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 45.4k | return ret; | 1604 | 45.4k | } |
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&) Line | Count | Source | 1465 | 25.0k | { | 1466 | 25.0k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 25.0k | std::vector<DepGraphIndex> ret; | 1469 | 25.0k | 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 | 25.0k | 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 | 25.0k | 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 | 25.0k | 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 | 25.0k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 25.0k | unsigned num_deps{0}; | 1485 | 421k | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 421k | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 421k | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 421k | num_deps += tx_deps[chl_idx]; | 1489 | 421k | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 421k | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 421k | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 421k | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 25.0k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 25.0k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 25.0k | auto it = chunk.begin(); | 1497 | 25.0k | DepGraphIndex ret = *it; | 1498 | 25.0k | ++it; | 1499 | 25.0k | while (it != chunk.end()) { | 1500 | 25.0k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 25.0k | ++it; | 1502 | 25.0k | } | 1503 | 25.0k | return ret; | 1504 | 25.0k | }; | 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 | 25.0k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 25.0k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 25.0k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 25.0k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 25.0k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 25.0k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 25.0k | if (a_feerate.size != b_feerate.size) { | 1517 | 25.0k | return a_feerate.size > b_feerate.size; | 1518 | 25.0k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 25.0k | auto fallback_cmp = fallback_order(a, b); | 1521 | 25.0k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 25.0k | Assume(false); | 1524 | 25.0k | return a < b; | 1525 | 25.0k | }; | 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 | 25.0k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 25.0k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 25.0k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 25.0k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 25.0k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 25.0k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 25.0k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 25.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 25.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 25.0k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 25.0k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 25.0k | Assume(false); | 1546 | 25.0k | return a.second < b.second; | 1547 | 25.0k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 138k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 138k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 35.4k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 35.4k | } | 1553 | 138k | } | 1554 | 25.0k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 163k | while (!ready_chunks.empty()) { | 1557 | 138k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 138k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 138k | ready_chunks.pop_back(); | 1560 | 138k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 138k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 138k | Assume(ready_tx.empty()); | 1564 | 421k | for (TxIdx tx_idx : chunk_txn) { | 1565 | 421k | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 421k | } | 1567 | 138k | Assume(!ready_tx.empty()); | 1568 | 138k | 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 | 559k | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 421k | auto tx_idx = ready_tx.front(); | 1574 | 421k | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 421k | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 421k | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 421k | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 844k | for (TxIdx chl_idx : tx_data.children) { | 1581 | 844k | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 844k | Assume(tx_deps[chl_idx] > 0); | 1584 | 844k | 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 | 225k | ready_tx.push_back(chl_idx); | 1587 | 225k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 225k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 844k | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 270k | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 270k | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 103k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 103k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 103k | } | 1597 | 270k | } | 1598 | 844k | } | 1599 | 421k | } | 1600 | 138k | } | 1601 | 25.0k | Assume(ret.size() == m_set_info.size()); | 1602 | 25.0k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 25.0k | return ret; | 1604 | 25.0k | } |
std::vector<unsigned int, std::allocator<unsigned int>> cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::GetLinearization<std::compare_three_way>(std::compare_three_way const&) Line | Count | Source | 1465 | 25.0k | { | 1466 | 25.0k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 25.0k | std::vector<DepGraphIndex> ret; | 1469 | 25.0k | 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 | 25.0k | 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 | 25.0k | 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 | 25.0k | 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 | 25.0k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 25.0k | unsigned num_deps{0}; | 1485 | 421k | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 421k | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 421k | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 421k | num_deps += tx_deps[chl_idx]; | 1489 | 421k | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 421k | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 421k | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 421k | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 25.0k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 25.0k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 25.0k | auto it = chunk.begin(); | 1497 | 25.0k | DepGraphIndex ret = *it; | 1498 | 25.0k | ++it; | 1499 | 25.0k | while (it != chunk.end()) { | 1500 | 25.0k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 25.0k | ++it; | 1502 | 25.0k | } | 1503 | 25.0k | return ret; | 1504 | 25.0k | }; | 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 | 25.0k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 25.0k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 25.0k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 25.0k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 25.0k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 25.0k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 25.0k | if (a_feerate.size != b_feerate.size) { | 1517 | 25.0k | return a_feerate.size > b_feerate.size; | 1518 | 25.0k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 25.0k | auto fallback_cmp = fallback_order(a, b); | 1521 | 25.0k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 25.0k | Assume(false); | 1524 | 25.0k | return a < b; | 1525 | 25.0k | }; | 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 | 25.0k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 25.0k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 25.0k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 25.0k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 25.0k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 25.0k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 25.0k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 25.0k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 25.0k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 25.0k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 25.0k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 25.0k | Assume(false); | 1546 | 25.0k | return a.second < b.second; | 1547 | 25.0k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 138k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 138k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 35.4k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 35.4k | } | 1553 | 138k | } | 1554 | 25.0k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 163k | while (!ready_chunks.empty()) { | 1557 | 138k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 138k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 138k | ready_chunks.pop_back(); | 1560 | 138k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 138k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 138k | Assume(ready_tx.empty()); | 1564 | 421k | for (TxIdx tx_idx : chunk_txn) { | 1565 | 421k | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 421k | } | 1567 | 138k | Assume(!ready_tx.empty()); | 1568 | 138k | 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 | 559k | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 421k | auto tx_idx = ready_tx.front(); | 1574 | 421k | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 421k | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 421k | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 421k | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 844k | for (TxIdx chl_idx : tx_data.children) { | 1581 | 844k | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 844k | Assume(tx_deps[chl_idx] > 0); | 1584 | 844k | 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 | 225k | ready_tx.push_back(chl_idx); | 1587 | 225k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 225k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 844k | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 270k | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 270k | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 103k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 103k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 103k | } | 1597 | 270k | } | 1598 | 844k | } | 1599 | 421k | } | 1600 | 138k | } | 1601 | 25.0k | Assume(ret.size() == m_set_info.size()); | 1602 | 25.0k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 25.0k | return ret; | 1604 | 25.0k | } |
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 | 5.20k | { | 1466 | 5.20k | m_cost.GetLinearizationBegin(); | 1467 | | /** The output linearization. */ | 1468 | 5.20k | std::vector<DepGraphIndex> ret; | 1469 | 5.20k | 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 | 5.20k | 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 | 5.20k | 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 | 5.20k | 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 | 5.20k | std::vector<TxIdx> ready_tx; | 1483 | | // Populate chunk_deps and tx_deps. | 1484 | 5.20k | unsigned num_deps{0}; | 1485 | 68.8k | for (TxIdx chl_idx : m_transaction_idxs) { | 1486 | 68.8k | const auto& chl_data = m_tx_data[chl_idx]; | 1487 | 68.8k | tx_deps[chl_idx] = chl_data.parents.Count(); | 1488 | 68.8k | num_deps += tx_deps[chl_idx]; | 1489 | 68.8k | auto chl_chunk_idx = chl_data.chunk_idx; | 1490 | 68.8k | auto& chl_chunk_info = m_set_info[chl_chunk_idx]; | 1491 | 68.8k | chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count(); | 1492 | 68.8k | } | 1493 | | /** Function to compute the highest element of a chunk, by fallback_order. */ | 1494 | 5.20k | auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept { | 1495 | 5.20k | auto& chunk = m_set_info[chunk_idx].transactions; | 1496 | 5.20k | auto it = chunk.begin(); | 1497 | 5.20k | DepGraphIndex ret = *it; | 1498 | 5.20k | ++it; | 1499 | 5.20k | while (it != chunk.end()) { | 1500 | 5.20k | if (fallback_order(*it, ret) > 0) ret = *it; | 1501 | 5.20k | ++it; | 1502 | 5.20k | } | 1503 | 5.20k | return ret; | 1504 | 5.20k | }; | 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 | 5.20k | auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1508 | | // Bail out for identical transactions. | 1509 | 5.20k | if (a == b) return false; | 1510 | | // First sort by increasing transaction feerate. | 1511 | 5.20k | auto& a_feerate = m_depgraph.FeeRate(a); | 1512 | 5.20k | auto& b_feerate = m_depgraph.FeeRate(b); | 1513 | 5.20k | auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate); | 1514 | 5.20k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1515 | | // Then by decreasing transaction size. | 1516 | 5.20k | if (a_feerate.size != b_feerate.size) { | 1517 | 5.20k | return a_feerate.size > b_feerate.size; | 1518 | 5.20k | } | 1519 | | // Tie-break by decreasing fallback_order. | 1520 | 5.20k | auto fallback_cmp = fallback_order(a, b); | 1521 | 5.20k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1522 | | // This should not be hit, because fallback_order defines a strong ordering. | 1523 | 5.20k | Assume(false); | 1524 | 5.20k | return a < b; | 1525 | 5.20k | }; | 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 | 5.20k | auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept { | 1530 | | // Bail out for identical chunks. | 1531 | 5.20k | if (a.first == b.first) return false; | 1532 | | // First sort by increasing chunk feerate. | 1533 | 5.20k | auto& chunk_feerate_a = m_set_info[a.first].feerate; | 1534 | 5.20k | auto& chunk_feerate_b = m_set_info[b.first].feerate; | 1535 | 5.20k | auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b); | 1536 | 5.20k | if (feerate_cmp != 0) return feerate_cmp < 0; | 1537 | | // Then by decreasing chunk size. | 1538 | 5.20k | if (chunk_feerate_a.size != chunk_feerate_b.size) { | 1539 | 5.20k | return chunk_feerate_a.size > chunk_feerate_b.size; | 1540 | 5.20k | } | 1541 | | // Tie-break by decreasing fallback_order. | 1542 | 5.20k | auto fallback_cmp = fallback_order(a.second, b.second); | 1543 | 5.20k | if (fallback_cmp != 0) return fallback_cmp > 0; | 1544 | | // This should not be hit, because fallback_order defines a strong ordering. | 1545 | 5.20k | Assume(false); | 1546 | 5.20k | return a.second < b.second; | 1547 | 5.20k | }; | 1548 | | // Construct a heap with all chunks that have no out-of-chunk dependencies. | 1549 | 61.4k | for (SetIdx chunk_idx : m_chunk_idxs) { | 1550 | 61.4k | if (chunk_deps[chunk_idx] == 0) { | 1551 | 6.07k | ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx)); | 1552 | 6.07k | } | 1553 | 61.4k | } | 1554 | 5.20k | std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1555 | | // Pop chunks off the heap. | 1556 | 66.6k | while (!ready_chunks.empty()) { | 1557 | 61.4k | auto [chunk_idx, _rnd] = ready_chunks.front(); | 1558 | 61.4k | std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1559 | 61.4k | ready_chunks.pop_back(); | 1560 | 61.4k | Assume(chunk_deps[chunk_idx] == 0); | 1561 | 61.4k | const auto& chunk_txn = m_set_info[chunk_idx].transactions; | 1562 | | // Build heap of all includable transactions in chunk. | 1563 | 61.4k | Assume(ready_tx.empty()); | 1564 | 68.8k | for (TxIdx tx_idx : chunk_txn) { | 1565 | 68.8k | if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx); | 1566 | 68.8k | } | 1567 | 61.4k | Assume(!ready_tx.empty()); | 1568 | 61.4k | 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 | 130k | while (!ready_tx.empty()) { | 1572 | | // Pop an element from the tx_ready heap. | 1573 | 68.8k | auto tx_idx = ready_tx.front(); | 1574 | 68.8k | std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1575 | 68.8k | ready_tx.pop_back(); | 1576 | | // Append to linearization. | 1577 | 68.8k | ret.push_back(tx_idx); | 1578 | | // Decrement dependency counts. | 1579 | 68.8k | auto& tx_data = m_tx_data[tx_idx]; | 1580 | 68.8k | for (TxIdx chl_idx : tx_data.children) { | 1581 | 63.6k | auto& chl_data = m_tx_data[chl_idx]; | 1582 | | // Decrement tx dependency count. | 1583 | 63.6k | Assume(tx_deps[chl_idx] > 0); | 1584 | 63.6k | 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 | 7.32k | ready_tx.push_back(chl_idx); | 1587 | 7.32k | std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn); | 1588 | 7.32k | } | 1589 | | // Decrement chunk dependency count if this is out-of-chunk dependency. | 1590 | 63.6k | if (chl_data.chunk_idx != chunk_idx) { | 1591 | 56.2k | Assume(chunk_deps[chl_data.chunk_idx] > 0); | 1592 | 56.2k | if (--chunk_deps[chl_data.chunk_idx] == 0) { | 1593 | | // Child chunk has no dependencies left. Add it to the chunk heap. | 1594 | 55.3k | ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)); | 1595 | 55.3k | std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); | 1596 | 55.3k | } | 1597 | 56.2k | } | 1598 | 63.6k | } | 1599 | 68.8k | } | 1600 | 61.4k | } | 1601 | 5.20k | Assume(ret.size() == m_set_info.size()); | 1602 | 5.20k | m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps); | 1603 | 5.20k | return ret; | 1604 | 5.20k | } |
|
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 | | { |
1621 | | std::vector<FeeFrac> ret; |
1622 | | for (auto chunk_idx : m_chunk_idxs) { |
1623 | | ret.push_back(m_set_info[chunk_idx].feerate); |
1624 | | } |
1625 | | std::sort(ret.begin(), ret.end(), std::greater{}); |
1626 | | return ret; |
1627 | | } |
1628 | | |
1629 | | /** Determine how much work was performed so far. */ |
1630 | 5.07M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); }cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned long>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 1.52M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned int, 2u>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 1.37M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 8u>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 1.37M | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
cluster_linearize::SpanningForestState<bitset_detail::IntBitSet<unsigned int>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 397k | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
cluster_linearize::SpanningForestState<bitset_detail::MultiIntBitSet<unsigned char, 4u>, cluster_linearize::SFLDefaultCostModel>::GetCost() const Line | Count | Source | 1630 | 398k | uint64_t GetCost() const noexcept { return m_cost.GetCost(); } |
|
1631 | | |
1632 | | /** Verify internal consistency of the data structure. */ |
1633 | | void SanityCheck() const |
1634 | | { |
1635 | | // |
1636 | | // Verify dependency parent/child information, and build list of (active) dependencies. |
1637 | | // |
1638 | | std::vector<std::pair<TxIdx, TxIdx>> expected_dependencies; |
1639 | | std::vector<std::pair<TxIdx, TxIdx>> all_dependencies; |
1640 | | std::vector<std::pair<TxIdx, TxIdx>> active_dependencies; |
1641 | | for (auto parent_idx : m_depgraph.Positions()) { |
1642 | | for (auto child_idx : m_depgraph.GetReducedChildren(parent_idx)) { |
1643 | | expected_dependencies.emplace_back(parent_idx, child_idx); |
1644 | | } |
1645 | | } |
1646 | | for (auto tx_idx : m_transaction_idxs) { |
1647 | | for (auto child_idx : m_tx_data[tx_idx].children) { |
1648 | | all_dependencies.emplace_back(tx_idx, child_idx); |
1649 | | if (m_tx_data[tx_idx].active_children[child_idx]) { |
1650 | | active_dependencies.emplace_back(tx_idx, child_idx); |
1651 | | } |
1652 | | } |
1653 | | } |
1654 | | std::sort(expected_dependencies.begin(), expected_dependencies.end()); |
1655 | | std::sort(all_dependencies.begin(), all_dependencies.end()); |
1656 | | assert(expected_dependencies == all_dependencies); |
1657 | | |
1658 | | // |
1659 | | // Verify the chunks against the list of active dependencies |
1660 | | // |
1661 | | SetType chunk_cover; |
1662 | | for (auto chunk_idx : m_chunk_idxs) { |
1663 | | 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 | | for (auto tx_idx : chunk_info.transactions) { |
1667 | | assert(m_tx_data[tx_idx].chunk_idx == chunk_idx); |
1668 | | } |
1669 | | assert(!chunk_cover.Overlaps(chunk_info.transactions)); |
1670 | | 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 | | assert(chunk_info.transactions.Any()); |
1676 | | SetType expected_chunk = SetType::Singleton(chunk_info.transactions.First()); |
1677 | | while (true) { |
1678 | | auto old = expected_chunk; |
1679 | | size_t active_dep_count{0}; |
1680 | | for (const auto& [par, chl] : active_dependencies) { |
1681 | | if (expected_chunk[par] || expected_chunk[chl]) { |
1682 | | expected_chunk.Set(par); |
1683 | | expected_chunk.Set(chl); |
1684 | | ++active_dep_count; |
1685 | | } |
1686 | | } |
1687 | | if (old == expected_chunk) { |
1688 | | assert(expected_chunk.Count() == active_dep_count + 1); |
1689 | | break; |
1690 | | } |
1691 | | } |
1692 | | assert(chunk_info.transactions == expected_chunk); |
1693 | | // Verify the chunk's feerate. |
1694 | | assert(chunk_info.feerate == m_depgraph.FeeRate(chunk_info.transactions)); |
1695 | | // Verify the chunk's reachable transactions. |
1696 | | assert(m_reachable[chunk_idx] == GetReachable(expected_chunk)); |
1697 | | // Verify that the chunk's reachable transactions don't include its own transactions. |
1698 | | assert(!m_reachable[chunk_idx].first.Overlaps(chunk_info.transactions)); |
1699 | | assert(!m_reachable[chunk_idx].second.Overlaps(chunk_info.transactions)); |
1700 | | } |
1701 | | // Verify that together, the chunks cover all transactions. |
1702 | | assert(chunk_cover == m_depgraph.Positions()); |
1703 | | |
1704 | | // |
1705 | | // Verify transaction data. |
1706 | | // |
1707 | | assert(m_transaction_idxs == m_depgraph.Positions()); |
1708 | | for (auto tx_idx : m_transaction_idxs) { |
1709 | | const auto& tx_data = m_tx_data[tx_idx]; |
1710 | | // Verify it has a valid chunk index, and that chunk includes this transaction. |
1711 | | assert(m_chunk_idxs[tx_data.chunk_idx]); |
1712 | | assert(m_set_info[tx_data.chunk_idx].transactions[tx_idx]); |
1713 | | // Verify parents/children. |
1714 | | assert(tx_data.parents == m_depgraph.GetReducedParents(tx_idx)); |
1715 | | assert(tx_data.children == m_depgraph.GetReducedChildren(tx_idx)); |
1716 | | // Verify active_children is a subset of children. |
1717 | | 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 | | for (auto child_idx : tx_data.active_children) { |
1720 | | assert(tx_data.dep_top_idx[child_idx] < m_set_info.size()); |
1721 | | assert(!m_chunk_idxs[tx_data.dep_top_idx[child_idx]]); |
1722 | | } |
1723 | | } |
1724 | | |
1725 | | // |
1726 | | // Verify active dependencies' top sets. |
1727 | | // |
1728 | | 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 | | SetType expected_top = SetType::Singleton(par_idx); |
1734 | | while (true) { |
1735 | | auto old = expected_top; |
1736 | | size_t active_dep_count{0}; |
1737 | | for (const auto& [par2_idx, chl2_idx] : active_dependencies) { |
1738 | | if (par_idx == par2_idx && chl_idx == chl2_idx) continue; |
1739 | | if (expected_top[par2_idx] || expected_top[chl2_idx]) { |
1740 | | expected_top.Set(par2_idx); |
1741 | | expected_top.Set(chl2_idx); |
1742 | | ++active_dep_count; |
1743 | | } |
1744 | | } |
1745 | | if (old == expected_top) { |
1746 | | assert(expected_top.Count() == active_dep_count + 1); |
1747 | | break; |
1748 | | } |
1749 | | } |
1750 | | assert(!expected_top[chl_idx]); |
1751 | | auto& dep_top_info = m_set_info[m_tx_data[par_idx].dep_top_idx[chl_idx]]; |
1752 | | assert(dep_top_info.transactions == expected_top); |
1753 | | // Verify the top set's feerate. |
1754 | | assert(dep_top_info.feerate == m_depgraph.FeeRate(dep_top_info.transactions)); |
1755 | | } |
1756 | | |
1757 | | // |
1758 | | // Verify m_suboptimal_chunks. |
1759 | | // |
1760 | | SetType suboptimal_idxs; |
1761 | | for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) { |
1762 | | auto chunk_idx = m_suboptimal_chunks[i]; |
1763 | | assert(!suboptimal_idxs[chunk_idx]); |
1764 | | suboptimal_idxs.Set(chunk_idx); |
1765 | | } |
1766 | | assert(m_suboptimal_idxs == suboptimal_idxs); |
1767 | | |
1768 | | // |
1769 | | // Verify m_nonminimal_chunks. |
1770 | | // |
1771 | | SetType nonminimal_idxs; |
1772 | | for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) { |
1773 | | auto [chunk_idx, pivot, flags] = m_nonminimal_chunks[i]; |
1774 | | assert(m_tx_data[pivot].chunk_idx == chunk_idx); |
1775 | | assert(!nonminimal_idxs[chunk_idx]); |
1776 | | nonminimal_idxs.Set(chunk_idx); |
1777 | | } |
1778 | | assert(nonminimal_idxs.IsSubsetOf(m_chunk_idxs)); |
1779 | | } |
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 | 191k | { |
1811 | | /** Initialize a spanning forest data structure for this cluster. */ |
1812 | 191k | SpanningForestState forest(depgraph, rng_seed); |
1813 | 191k | if (!old_linearization.empty()) { |
1814 | 143k | forest.LoadLinearization(old_linearization); |
1815 | 143k | if (!is_topological) forest.MakeTopological(); |
1816 | 143k | } else { |
1817 | 47.4k | forest.MakeTopological(); |
1818 | 47.4k | } |
1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result |
1820 | | // is found. |
1821 | 191k | if (forest.GetCost() < max_cost) { |
1822 | 191k | forest.StartOptimizing(); |
1823 | 2.55M | do { |
1824 | 2.55M | if (!forest.OptimizeStep()) break; |
1825 | 2.55M | } while (forest.GetCost() < max_cost); |
1826 | 191k | } |
1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are |
1828 | | // minimal. |
1829 | 191k | bool optimal = false; |
1830 | 191k | if (forest.GetCost() < max_cost) { |
1831 | 191k | forest.StartMinimizing(); |
1832 | 2.32M | do { |
1833 | 2.32M | if (!forest.MinimizeStep()) { |
1834 | 191k | optimal = true; |
1835 | 191k | break; |
1836 | 191k | } |
1837 | 2.32M | } while (forest.GetCost() < max_cost); |
1838 | 191k | } |
1839 | 191k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; |
1840 | 191k | } std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::IntBitSet<unsigned long>, std::compare_three_way>(cluster_linearize::DepGraph<bitset_detail::IntBitSet<unsigned long>> const&, unsigned long, unsigned long, std::compare_three_way const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Line | Count | Source | 1810 | 45.4k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 45.4k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 45.4k | if (!old_linearization.empty()) { | 1814 | 33.7k | forest.LoadLinearization(old_linearization); | 1815 | 33.7k | if (!is_topological) forest.MakeTopological(); | 1816 | 33.7k | } else { | 1817 | 11.6k | forest.MakeTopological(); | 1818 | 11.6k | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 45.4k | if (forest.GetCost() < max_cost) { | 1822 | 45.4k | forest.StartOptimizing(); | 1823 | 733k | do { | 1824 | 733k | if (!forest.OptimizeStep()) break; | 1825 | 733k | } while (forest.GetCost() < max_cost); | 1826 | 45.4k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 45.4k | bool optimal = false; | 1830 | 45.4k | if (forest.GetCost() < max_cost) { | 1831 | 45.4k | forest.StartMinimizing(); | 1832 | 596k | do { | 1833 | 596k | if (!forest.MinimizeStep()) { | 1834 | 45.4k | optimal = true; | 1835 | 45.4k | break; | 1836 | 45.4k | } | 1837 | 596k | } while (forest.GetCost() < max_cost); | 1838 | 45.4k | } | 1839 | 45.4k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 45.4k | } |
std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::MultiIntBitSet<unsigned int, 2u>, std::compare_three_way>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned int, 2u>> const&, unsigned long, unsigned long, std::compare_three_way const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Line | Count | Source | 1810 | 45.4k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 45.4k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 45.4k | if (!old_linearization.empty()) { | 1814 | 33.7k | forest.LoadLinearization(old_linearization); | 1815 | 33.7k | if (!is_topological) forest.MakeTopological(); | 1816 | 33.7k | } else { | 1817 | 11.6k | forest.MakeTopological(); | 1818 | 11.6k | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 45.4k | if (forest.GetCost() < max_cost) { | 1822 | 45.4k | forest.StartOptimizing(); | 1823 | 733k | do { | 1824 | 733k | if (!forest.OptimizeStep()) break; | 1825 | 733k | } while (forest.GetCost() < max_cost); | 1826 | 45.4k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 45.4k | bool optimal = false; | 1830 | 45.4k | if (forest.GetCost() < max_cost) { | 1831 | 45.4k | forest.StartMinimizing(); | 1832 | 596k | do { | 1833 | 596k | if (!forest.MinimizeStep()) { | 1834 | 45.4k | optimal = true; | 1835 | 45.4k | break; | 1836 | 45.4k | } | 1837 | 596k | } while (forest.GetCost() < max_cost); | 1838 | 45.4k | } | 1839 | 45.4k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 45.4k | } |
std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::MultiIntBitSet<unsigned char, 8u>, std::compare_three_way>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 8u>> const&, unsigned long, unsigned long, std::compare_three_way const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Line | Count | Source | 1810 | 45.4k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 45.4k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 45.4k | if (!old_linearization.empty()) { | 1814 | 33.8k | forest.LoadLinearization(old_linearization); | 1815 | 33.8k | if (!is_topological) forest.MakeTopological(); | 1816 | 33.8k | } else { | 1817 | 11.5k | forest.MakeTopological(); | 1818 | 11.5k | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 45.4k | if (forest.GetCost() < max_cost) { | 1822 | 45.4k | forest.StartOptimizing(); | 1823 | 735k | do { | 1824 | 735k | if (!forest.OptimizeStep()) break; | 1825 | 735k | } while (forest.GetCost() < max_cost); | 1826 | 45.4k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 45.4k | bool optimal = false; | 1830 | 45.4k | if (forest.GetCost() < max_cost) { | 1831 | 45.4k | forest.StartMinimizing(); | 1832 | 596k | do { | 1833 | 596k | if (!forest.MinimizeStep()) { | 1834 | 45.4k | optimal = true; | 1835 | 45.4k | break; | 1836 | 45.4k | } | 1837 | 596k | } while (forest.GetCost() < max_cost); | 1838 | 45.4k | } | 1839 | 45.4k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 45.4k | } |
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) Line | Count | Source | 1810 | 25.0k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 25.0k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 25.0k | if (!old_linearization.empty()) { | 1814 | 18.7k | forest.LoadLinearization(old_linearization); | 1815 | 18.7k | if (!is_topological) forest.MakeTopological(); | 1816 | 18.7k | } else { | 1817 | 6.28k | forest.MakeTopological(); | 1818 | 6.28k | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 25.0k | if (forest.GetCost() < max_cost) { | 1822 | 25.0k | forest.StartOptimizing(); | 1823 | 171k | do { | 1824 | 171k | if (!forest.OptimizeStep()) break; | 1825 | 171k | } while (forest.GetCost() < max_cost); | 1826 | 25.0k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 25.0k | bool optimal = false; | 1830 | 25.0k | if (forest.GetCost() < max_cost) { | 1831 | 25.0k | forest.StartMinimizing(); | 1832 | 200k | do { | 1833 | 200k | if (!forest.MinimizeStep()) { | 1834 | 25.0k | optimal = true; | 1835 | 25.0k | break; | 1836 | 25.0k | } | 1837 | 200k | } while (forest.GetCost() < max_cost); | 1838 | 25.0k | } | 1839 | 25.0k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 25.0k | } |
std::tuple<std::vector<unsigned int, std::allocator<unsigned int>>, bool, unsigned long> cluster_linearize::Linearize<bitset_detail::MultiIntBitSet<unsigned char, 4u>, std::compare_three_way>(cluster_linearize::DepGraph<bitset_detail::MultiIntBitSet<unsigned char, 4u>> const&, unsigned long, unsigned long, std::compare_three_way const&, std::span<unsigned int const, 18446744073709551615ul>, bool) Line | Count | Source | 1810 | 25.0k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 25.0k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 25.0k | if (!old_linearization.empty()) { | 1814 | 18.6k | forest.LoadLinearization(old_linearization); | 1815 | 18.6k | if (!is_topological) forest.MakeTopological(); | 1816 | 18.6k | } else { | 1817 | 6.31k | forest.MakeTopological(); | 1818 | 6.31k | } | 1819 | | // Make improvement steps to it until we hit the max_iterations limit, or an optimal result | 1820 | | // is found. | 1821 | 25.0k | if (forest.GetCost() < max_cost) { | 1822 | 25.0k | forest.StartOptimizing(); | 1823 | 172k | do { | 1824 | 172k | if (!forest.OptimizeStep()) break; | 1825 | 172k | } while (forest.GetCost() < max_cost); | 1826 | 25.0k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 25.0k | bool optimal = false; | 1830 | 25.0k | if (forest.GetCost() < max_cost) { | 1831 | 25.0k | forest.StartMinimizing(); | 1832 | 200k | do { | 1833 | 200k | if (!forest.MinimizeStep()) { | 1834 | 25.0k | optimal = true; | 1835 | 25.0k | break; | 1836 | 25.0k | } | 1837 | 200k | } while (forest.GetCost() < max_cost); | 1838 | 25.0k | } | 1839 | 25.0k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 25.0k | } |
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 | 5.20k | { | 1811 | | /** Initialize a spanning forest data structure for this cluster. */ | 1812 | 5.20k | SpanningForestState forest(depgraph, rng_seed); | 1813 | 5.20k | if (!old_linearization.empty()) { | 1814 | 5.20k | forest.LoadLinearization(old_linearization); | 1815 | 5.20k | if (!is_topological) forest.MakeTopological(); | 1816 | 5.20k | } 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 | 5.20k | if (forest.GetCost() < max_cost) { | 1822 | 5.20k | forest.StartOptimizing(); | 1823 | 10.9k | do { | 1824 | 10.9k | if (!forest.OptimizeStep()) break; | 1825 | 10.9k | } while (forest.GetCost() < max_cost); | 1826 | 5.20k | } | 1827 | | // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are | 1828 | | // minimal. | 1829 | 5.20k | bool optimal = false; | 1830 | 5.20k | if (forest.GetCost() < max_cost) { | 1831 | 5.20k | forest.StartMinimizing(); | 1832 | 131k | do { | 1833 | 131k | if (!forest.MinimizeStep()) { | 1834 | 5.20k | optimal = true; | 1835 | 5.20k | break; | 1836 | 5.20k | } | 1837 | 131k | } while (forest.GetCost() < max_cost); | 1838 | 5.20k | } | 1839 | 5.20k | return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()}; | 1840 | 5.20k | } |
|
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 | 5.20k | { |
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 | 5.20k | static constexpr DepGraphIndex SENTINEL{0}; |
1898 | | /** Indicator that a group has no previous transaction. */ |
1899 | 5.20k | static constexpr DepGraphIndex NO_PREV_TX{0}; |
1900 | | |
1901 | | |
1902 | | /** Data structure per transaction entry. */ |
1903 | 5.20k | struct TxEntry |
1904 | 5.20k | { |
1905 | | /** The index of the previous transaction in this group; NO_PREV_TX if this is the first |
1906 | | * entry of a group. */ |
1907 | 5.20k | 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 | 5.20k | 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 | 5.20k | DepGraphIndex prev_group; |
1917 | | /** All transactions in the group. Empty for the sentinel. */ |
1918 | 5.20k | SetType group; |
1919 | | /** All dependencies of the group (descendants in even passes; ancestors in odd ones). */ |
1920 | 5.20k | SetType deps; |
1921 | | /** The combined fee/size of transactions in the group. Fee is negated in even passes. */ |
1922 | 5.20k | FeeFrac feerate; |
1923 | 5.20k | }; |
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 | 5.20k | std::vector<TxEntry> entries(depgraph.PositionRange() + 1); |
1950 | | |
1951 | | // Perform two passes over the linearization. |
1952 | 15.6k | for (int pass = 0; pass < 2; ++pass) { |
1953 | 10.4k | int rev = !(pass & 1); |
1954 | | // Construct a sentinel group, identifying the start of the list. |
1955 | 10.4k | entries[SENTINEL].prev_group = SENTINEL; |
1956 | 10.4k | Assume(entries[SENTINEL].feerate.IsEmpty()); |
1957 | | |
1958 | | // Iterate over all elements in the existing linearization. |
1959 | 148k | for (DepGraphIndex i = 0; i < linearization.size(); ++i) { |
1960 | | // Even passes are from back to front; odd passes from front to back. |
1961 | 137k | DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i]; |
1962 | | // Construct a new group containing just idx. In even passes, the meaning of |
1963 | | // parent/child and high/low feerate are swapped. |
1964 | 137k | DepGraphIndex cur_group = idx + 1; |
1965 | 137k | entries[cur_group].group = SetType::Singleton(idx); |
1966 | 137k | entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx); |
1967 | 137k | entries[cur_group].feerate = depgraph.FeeRate(idx); |
1968 | 137k | if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee; |
1969 | 137k | entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group. |
1970 | 137k | 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 | 137k | entries[cur_group].prev_group = entries[SENTINEL].prev_group; |
1973 | 137k | entries[SENTINEL].prev_group = cur_group; |
1974 | | |
1975 | | // Start merge/swap cycle. |
1976 | 137k | DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel. |
1977 | 137k | DepGraphIndex prev_group = entries[cur_group].prev_group; |
1978 | | // Continue as long as the current group has higher feerate than the previous one. |
1979 | 152k | 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 | 14.9k | Assume(cur_group == entries[next_group].prev_group); |
1983 | 14.9k | Assume(prev_group == entries[cur_group].prev_group); |
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 | 14.9k | Assume(cur_group != SENTINEL); |
1988 | 14.9k | Assume(prev_group != SENTINEL); |
1989 | 14.9k | 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 | 14.9k | entries[cur_group].group |= entries[prev_group].group; |
1994 | 14.9k | entries[cur_group].deps |= entries[prev_group].deps; |
1995 | 14.9k | 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 | 14.9k | 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 | 14.9k | entries[cur_group].first_tx = entries[prev_group].first_tx; |
2000 | | // The previous group becomes whatever group was before the former one. |
2001 | 14.9k | prev_group = entries[prev_group].prev_group; |
2002 | 14.9k | entries[cur_group].prev_group = prev_group; |
2003 | 14.9k | } 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 | 14.9k | } |
2017 | 137k | } |
2018 | | |
2019 | | // Convert the entries back to linearization (overwriting the existing one). |
2020 | 10.4k | DepGraphIndex cur_group = entries[0].prev_group; |
2021 | 10.4k | DepGraphIndex done = 0; |
2022 | 133k | while (cur_group != SENTINEL) { |
2023 | 122k | 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 | 122k | if (rev) { |
2027 | 68.8k | do { |
2028 | 68.8k | *(linearization.begin() + (done++)) = cur_tx - 1; |
2029 | 68.8k | cur_tx = entries[cur_tx].prev_tx; |
2030 | 68.8k | } while (cur_tx != NO_PREV_TX); |
2031 | 61.4k | } else { |
2032 | 68.8k | do { |
2033 | 68.8k | *(linearization.end() - (++done)) = cur_tx - 1; |
2034 | 68.8k | cur_tx = entries[cur_tx].prev_tx; |
2035 | 68.8k | } while (cur_tx != NO_PREV_TX); |
2036 | 61.4k | } |
2037 | 122k | cur_group = entries[cur_group].prev_group; |
2038 | 122k | } |
2039 | 10.4k | Assume(done == linearization.size()); |
2040 | 10.4k | } |
2041 | 5.20k | } |
2042 | | |
2043 | | } // namespace cluster_linearize |
2044 | | |
2045 | | #endif // BITCOIN_CLUSTER_LINEARIZE_H |