99#include < util/bitset.h>
1010#include < util/check.h>
1111#include < util/feefrac.h>
12+ #include < util/vector.h>
1213
1314#include < compare>
1415#include < memory>
@@ -322,6 +323,8 @@ class TxGraphImpl final : public TxGraph
322323 ChunkIndex m_main_chunkindex;
323324 /* * Number of index-observing objects in existence (BlockBuilderImpls). */
324325 size_t m_main_chunkindex_observers{0 };
326+ /* * Cache of discarded ChunkIndex node handles to re-use, avoiding additional allocation. */
327+ std::vector<ChunkIndex::node_type> m_main_chunkindex_discarded;
325328
326329 /* * A Locator that describes whether, where, and in which Cluster an Entry appears.
327330 * Every Entry has MAX_LEVELS locators, as it may appear in one Cluster per level.
@@ -441,6 +444,10 @@ class TxGraphImpl final : public TxGraph
441444 void ClearLocator (int level, GraphIndex index) noexcept ;
442445 /* * Find which Clusters in main conflict with ones in staging. */
443446 std::vector<Cluster*> GetConflicts () const noexcept ;
447+ /* * Clear an Entry's ChunkData. */
448+ void ClearChunkData (Entry& entry) noexcept ;
449+ /* * Give an Entry a ChunkData object. */
450+ void CreateChunkData (GraphIndex idx, LinearizationIndex chunk_count) noexcept ;
444451
445452 // Functions for handling Refs.
446453
@@ -594,6 +601,37 @@ class BlockBuilderImpl final : public TxGraph::BlockBuilder
594601 void Skip () noexcept final ;
595602};
596603
604+ void TxGraphImpl::ClearChunkData (Entry& entry) noexcept
605+ {
606+ if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end ()) {
607+ Assume (m_main_chunkindex_observers == 0 );
608+ // If the Entry has a non-empty m_main_chunkindex_iterator, extract it, and move the handle
609+ // to the cache of discarded chunkindex entries.
610+ m_main_chunkindex_discarded.emplace_back (m_main_chunkindex.extract (entry.m_main_chunkindex_iterator ));
611+ entry.m_main_chunkindex_iterator = m_main_chunkindex.end ();
612+ }
613+ }
614+
615+ void TxGraphImpl::CreateChunkData (GraphIndex idx, LinearizationIndex chunk_count) noexcept
616+ {
617+ auto & entry = m_entries[idx];
618+ if (!m_main_chunkindex_discarded.empty ()) {
619+ // Reuse an discarded node handle.
620+ auto & node = m_main_chunkindex_discarded.back ().value ();
621+ node.m_graph_index = idx;
622+ node.m_chunk_count = chunk_count;
623+ auto insert_result = m_main_chunkindex.insert (std::move (m_main_chunkindex_discarded.back ()));
624+ Assume (insert_result.inserted );
625+ entry.m_main_chunkindex_iterator = insert_result.position ;
626+ m_main_chunkindex_discarded.pop_back ();
627+ } else {
628+ // Construct a new entry.
629+ auto emplace_result = m_main_chunkindex.emplace (idx, chunk_count);
630+ Assume (emplace_result.second );
631+ entry.m_main_chunkindex_iterator = emplace_result.first ;
632+ }
633+ }
634+
597635void TxGraphImpl::ClearLocator (int level, GraphIndex idx) noexcept
598636{
599637 auto & entry = m_entries[idx];
@@ -616,25 +654,17 @@ void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
616654 --m_staging_clusterset->m_txcount ;
617655 }
618656 }
619- if (level == 0 && entry.m_main_chunkindex_iterator != m_main_chunkindex.end ()) {
620- Assume (m_main_chunkindex_observers == 0 );
621- m_main_chunkindex.erase (entry.m_main_chunkindex_iterator );
622- entry.m_main_chunkindex_iterator = m_main_chunkindex.end ();
623- }
657+ if (level == 0 ) ClearChunkData (entry);
624658}
625659
626660void Cluster::Updated (TxGraphImpl& graph) noexcept
627661{
628662 // Update all the Locators for this Cluster's Entry objects.
629663 for (DepGraphIndex idx : m_linearization) {
630664 auto & entry = graph.m_entries [m_mapping[idx]];
631- if (m_level == 0 && entry.m_main_chunkindex_iterator != graph.m_main_chunkindex .end ()) {
632- // Destroy any potential ChunkData prior to modifying the Cluster (as that could
633- // invalidate its ordering).
634- Assume (graph.m_main_chunkindex_observers == 0 );
635- graph.m_main_chunkindex .erase (entry.m_main_chunkindex_iterator );
636- entry.m_main_chunkindex_iterator = graph.m_main_chunkindex .end ();
637- }
665+ // Discard any potential ChunkData prior to modifying the Cluster (as that could
666+ // invalidate its ordering).
667+ if (m_level == 0 ) graph.ClearChunkData (entry);
638668 entry.m_locator [m_level].SetPresent (this , idx);
639669 }
640670 // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
@@ -661,9 +691,7 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
661691 chunk.transactions .Reset (idx);
662692 if (chunk.transactions .None ()) {
663693 // Last transaction in the chunk.
664- auto [it, inserted] = graph.m_main_chunkindex .emplace (graph_idx, chunk_count);
665- Assume (inserted);
666- entry.m_main_chunkindex_iterator = it;
694+ graph.CreateChunkData (graph_idx, chunk_count);
667695 break ;
668696 }
669697 }
@@ -922,13 +950,9 @@ void Cluster::Merge(TxGraphImpl& graph, Cluster& other) noexcept
922950 // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
923951 // merged Cluster later anyway).
924952 auto & entry = graph.m_entries [idx];
925- if (m_level == 0 && entry.m_main_chunkindex_iterator != graph.m_main_chunkindex .end ()) {
926- // Destroy any potential ChunkData prior to modifying the Cluster (as that could
927- // invalidate its ordering).
928- Assume (graph.m_main_chunkindex_observers == 0 );
929- graph.m_main_chunkindex .erase (entry.m_main_chunkindex_iterator );
930- entry.m_main_chunkindex_iterator = graph.m_main_chunkindex .end ();
931- }
953+ // Discard any potential ChunkData prior to modifying the Cluster (as that could
954+ // invalidate its ordering).
955+ if (m_level == 0 ) graph.ClearChunkData (entry);
932956 entry.m_locator [m_level].SetPresent (this , new_pos);
933957 }
934958 // Purge the other Cluster, now that everything has been moved.
@@ -1171,6 +1195,9 @@ void TxGraphImpl::Compact() noexcept
11711195 if (!m_staging_clusterset->m_removed .empty ()) return ;
11721196 }
11731197
1198+ // Release memory used by discarded ChunkData index entries.
1199+ ClearShrink (m_main_chunkindex_discarded);
1200+
11741201 // Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
11751202 // ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
11761203 // later-processed ones during the "swap with end of m_entries" step below (which might
0 commit comments