| 1854 | } |
| 1855 | |
| 1856 | void TxGraphImpl::GroupClusters(int level) noexcept |
| 1857 | { |
| 1858 | auto& clusterset = GetClusterSet(level); |
| 1859 | // If the groupings have been computed already, nothing is left to be done. |
| 1860 | if (clusterset.m_group_data.has_value()) return; |
| 1861 | |
| 1862 | // Before computing which Clusters need to be merged together, first apply all removals and |
| 1863 | // split the Clusters into connected components. If we would group first, we might end up |
| 1864 | // with inefficient and/or oversized Clusters which just end up being split again anyway. |
| 1865 | SplitAll(level); |
| 1866 | |
| 1867 | /** Annotated clusters: an entry for each Cluster, together with the sequence number for the |
| 1868 | * representative for the partition it is in (initially its own, later that of the |
| 1869 | * to-be-merged group). */ |
| 1870 | std::vector<std::pair<Cluster*, uint64_t>> an_clusters; |
| 1871 | /** Annotated dependencies: an entry for each m_deps_to_add entry (excluding ones that apply |
| 1872 | * to removed transactions), together with the sequence number of the representative root of |
| 1873 | * Clusters it applies to (initially that of the child Cluster, later that of the |
| 1874 | * to-be-merged group). */ |
| 1875 | std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t>> an_deps; |
| 1876 | |
| 1877 | // Construct an an_clusters entry for every oversized cluster, including ones from levels below, |
| 1878 | // as they may be inherited in this one. |
| 1879 | for (int level_iter = 0; level_iter <= level; ++level_iter) { |
| 1880 | for (auto& cluster : GetClusterSet(level_iter).m_clusters[int(QualityLevel::OVERSIZED_SINGLETON)]) { |
| 1881 | auto graph_idx = cluster->GetClusterEntry(0); |
| 1882 | auto cur_cluster = FindCluster(graph_idx, level); |
| 1883 | if (cur_cluster == nullptr) continue; |
| 1884 | an_clusters.emplace_back(cur_cluster, cur_cluster->m_sequence); |
| 1885 | } |
| 1886 | } |
| 1887 | |
| 1888 | // Construct a an_clusters entry for every parent and child in the to-be-applied dependencies, |
| 1889 | // and an an_deps entry for each dependency to be applied. |
| 1890 | an_deps.reserve(clusterset.m_deps_to_add.size()); |
| 1891 | for (const auto& [par, chl] : clusterset.m_deps_to_add) { |
| 1892 | auto par_cluster = FindCluster(par, level); |
| 1893 | auto chl_cluster = FindCluster(chl, level); |
| 1894 | // Skip dependencies for which the parent or child transaction is removed. |
| 1895 | if (par_cluster == nullptr || chl_cluster == nullptr) continue; |
| 1896 | an_clusters.emplace_back(par_cluster, par_cluster->m_sequence); |
| 1897 | // Do not include a duplicate when parent and child are identical, as it'll be removed |
| 1898 | // below anyway. |
| 1899 | if (chl_cluster != par_cluster) an_clusters.emplace_back(chl_cluster, chl_cluster->m_sequence); |
| 1900 | // Add entry to an_deps, using the child sequence number. |
| 1901 | an_deps.emplace_back(std::pair{par, chl}, chl_cluster->m_sequence); |
| 1902 | } |
| 1903 | // Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters |
| 1904 | // to which dependencies apply, or which are oversized. |
| 1905 | std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; }); |
| 1906 | an_clusters.erase(std::ranges::unique(an_clusters).begin(), an_clusters.end()); |
| 1907 | // Sort an_deps by applying the same order to the involved child cluster. |
| 1908 | std::ranges::sort(an_deps, [&](auto& a, auto& b) noexcept { return a.second < b.second; }); |
| 1909 | |
| 1910 | // Run the union-find algorithm to find partitions of the input Clusters which need to be |
| 1911 | // grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure. |
| 1912 | { |
| 1913 | /** Each PartitionData entry contains information about a single input Cluster. */ |
nothing calls this directly
no test coverage detected