Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cluster mempool: merging & postprocessing of linearizations #30285

Merged
merged 5 commits into from
Aug 5, 2024

Conversation

sipa
Copy link
Member

@sipa sipa commented Jun 14, 2024

Part of cluster mempool: #30289

Depends on #30126, and was split off from it. #28676 depends on this.

This adds the algorithms for merging & postprocessing linearizations.

The PostLinearize(depgraph, linearization) function performs an in-place improvement of linearization, using two iterations of the Linearization post-processing algorithm. The first running from back to front, the second from front to back.

The MergeLinearizations(depgraph, linearization1, linearization2) function computes a new linearization for the provided cluster, given two existing linearizations for that cluster, which is at least as good as both inputs. The algorithm is described at a high level in merging incomparable linearizations.

For background and references, see Introduction to cluster linearization.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 14, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK glozow, instagibbs, sdaftuar

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #28676 ([WIP] Cluster mempool implementation by sdaftuar)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@sipa sipa changed the title Low-level cluster linearization code: merging & postprocessing cluster mempool: merging and postprocessing for linearizations Jun 14, 2024
@sipa sipa changed the title cluster mempool: merging and postprocessing for linearizations cluster mempool: merging & postprocessing of linearizations Jun 14, 2024
@sipa sipa added the Mempool label Jun 14, 2024
@sipa sipa force-pushed the 202406_clusterlin_meta branch from 09aa7a8 to 7ab4c8e Compare July 2, 2024 20:51
@sipa sipa force-pushed the 202406_clusterlin_meta branch 2 times, most recently from 5a7b77c to b9ae506 Compare July 10, 2024 20:49
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/27291659212

@sipa sipa force-pushed the 202406_clusterlin_meta branch 2 times, most recently from c2acefe to df165d3 Compare July 11, 2024 12:37
@sipa sipa force-pushed the 202406_clusterlin_meta branch 2 times, most recently from 3174292 to 20ea5d8 Compare July 19, 2024 19:38
@sipa sipa force-pushed the 202406_clusterlin_meta branch from 20ea5d8 to f4a183c Compare July 26, 2024 12:39
Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f4a183c

did not yet review implementation of PostLinearize, didn't verify benchmarks with optimizations

src/cluster_linearize.h Outdated Show resolved Hide resolved
src/cluster_linearize.h Show resolved Hide resolved
src/cluster_linearize.h Outdated Show resolved Hide resolved
src/test/fuzz/cluster_linearize.cpp Outdated Show resolved Hide resolved
src/test/fuzz/cluster_linearize.cpp Show resolved Hide resolved
src/test/fuzz/cluster_linearize.cpp Show resolved Hide resolved

FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
{
// Verify that taking an existing linearization, and moving a leaf to the back, potentially
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this specifically for prioritisetransaction or something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it means that a particular approach for RBF will never worsen single-transaction leaf replacements that do not change the size of a transaction. I've added a comment to clarify.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my initial thought but the size isn't changing so I'm unsure how it would map to an RBF?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"laboratory conditions" I guess. It's just a nice property that holds; even if the conditions for it don't exactly hold often in reality, it probably means they're not far off. I'll look into whether I can generalize it a bit to support changing size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add on to that -- at one point in an earlier draft of #28676, we were seeing examples of RBFs where a single transaction was being replaced with one that had identical size, identical parents, but higher fee, yet due to quirks in how linearization worked the replacement was being rejected because the diagram wasn't improving.

We've since generalized this concern around accidentally discovering a better linearization for a cluster while processing a potential replacement, and I think we will be addressing this in a more robust way, but it's nice that PostLinearization solves the specific problem we observed in the wild.

src/test/fuzz/cluster_linearize.cpp Outdated Show resolved Hide resolved
src/cluster_linearize.h Outdated Show resolved Hide resolved
src/cluster_linearize.h Show resolved Hide resolved
@sipa sipa force-pushed the 202406_clusterlin_meta branch from f4a183c to 157464f Compare July 30, 2024 22:08
Copy link
Member

@sdaftuar sdaftuar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code review ACK 157464f, will fuzz

src/cluster_linearize.h Show resolved Hide resolved
}
if (parents.Any()) depgraph_tree.AddDependency(parents.First(), i);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to test my understanding: at this point, the graph we've constructed may not be connected, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly, for two reasons:

  • depgraph_gen may not be connected to begin with (there is no MakeConnected(depgraph_gen); perhaps there should be?)
  • Even if depgraph_gen is connected (imagine it being a trellis for example), removing all but the first parent, or all but the first child, may split it up.


FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
{
// Verify that taking an existing linearization, and moving a leaf to the back, potentially
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add on to that -- at one point in an earlier draft of #28676, we were seeing examples of RBFs where a single transaction was being replaced with one that had identical size, identical parents, but higher fee, yet due to quirks in how linearization worked the replacement was being rejected because the diagram wasn't improving.

We've since generalized this concern around accidentally discovering a better linearization for a cluster while processing a potential replacement, and I think we will be addressing this in a more robust way, but it's nice that PostLinearization solves the specific problem we observed in the wild.

src/test/fuzz/cluster_linearize.cpp Outdated Show resolved Hide resolved
@sdaftuar
Copy link
Member

ACK 157464f

Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 157464f

I didn't verify benchmark timings but opts seemed ok

src/cluster_linearize.h Outdated Show resolved Hide resolved
assert(cmp >= 0);

// The chunks that come out of postlinearizing are always connected.
LinearizationChunking linchunking(depgraph, post_linearization);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like a tasty cantonese restaurant

* Specifically, this finds the connected component which contains the first transaction of
* todo (if any).
*
* Two transactions are considered connected if there is a path from one to the other inside
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Two transactions are considered connected if there is a path from one to the other inside
* Two transactions are considered connected if there is a path from one to the other and both are inside

?

in general this description and rewrite was very helpful

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just the two transactions, but all transactions in the path need to be in todo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have rewritten this. Please have a look if it's better.

Copy link
Member

@instagibbs instagibbs Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely clearer on what the "path" is 👍 (and matches my understanding of the code now)

src/cluster_linearize.h Outdated Show resolved Hide resolved
src/cluster_linearize.h Outdated Show resolved Hide resolved
src/cluster_linearize.h Show resolved Hide resolved
depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
auto new_chunking = ChunkLinearization(depgraph, lin_moved);
auto cmp = CompareChunks(new_chunking, old_chunking);
assert(cmp >= 0);
Copy link
Member

@instagibbs instagibbs Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice to have a non-tree example of things strictly improving, also interesting to see that improvements still happen even if no fees are added
edit: hm, still not quite showing strict improvement since fees are after the fact; it's a different "cluster". Oh well.

Suggested change
assert(cmp >= 0);
if (fee_inc > 0) {
// It's more fees; should be superior
assert(cmp > 0);
} else {
assert(cmp >= 0);
}

@sdaftuar
Copy link
Member

sdaftuar commented Aug 1, 2024

Here are my linearization benchmarks (ryzen 7995wx) after this PR (compare with #30126 (comment)):

ns/op op/s err% total benchmark
973.43 1,027,297.78 0.5% 0.01 LinearizeNoIters16TxWorstCaseAnc
1,464.85 682,664.84 0.2% 0.01 LinearizeNoIters16TxWorstCaseLIMO
2,503.93 399,372.47 0.3% 0.01 LinearizeNoIters32TxWorstCaseAnc
5,091.86 196,392.05 0.2% 0.01 LinearizeNoIters32TxWorstCaseLIMO
4,881.34 204,861.99 0.1% 0.01 LinearizeNoIters48TxWorstCaseAnc
10,880.48 91,907.74 0.0% 0.01 LinearizeNoIters48TxWorstCaseLIMO
7,843.67 127,491.30 0.0% 0.01 LinearizeNoIters64TxWorstCaseAnc
18,803.07 53,182.80 0.1% 0.01 LinearizeNoIters64TxWorstCaseLIMO
11,747.48 85,124.62 0.1% 0.01 LinearizeNoIters75TxWorstCaseAnc
28,902.79 34,598.74 0.0% 0.01 LinearizeNoIters75TxWorstCaseLIMO
19,030.02 52,548.55 0.1% 0.01 LinearizeNoIters99TxWorstCaseAnc
49,663.43 20,135.54 0.1% 0.01 LinearizeNoIters99TxWorstCaseLIMO
ns/iters iters/s err% total benchmark
13.66 73,231,349.71 0.1% 0.01 LinearizePerIter16TxWorstCase
9.63 103,869,988.01 0.3% 0.01 LinearizePerIter32TxWorstCase
9.38 106,626,518.23 0.3% 0.01 LinearizePerIter48TxWorstCase
9.44 105,920,646.37 0.3% 0.01 LinearizePerIter64TxWorstCase
10.38 96,365,108.12 0.3% 0.01 LinearizePerIter75TxWorstCase
10.38 96,374,023.73 0.2% 0.01 LinearizePerIter99TxWorstCase
ns/op op/s err% total benchmark
690.49 1,448,239.70 0.4% 0.01 MergeLinearizations16TxWorstCase
2,703.90 369,835.96 0.1% 0.01 MergeLinearizations32TxWorstCase
6,143.66 162,769.31 0.0% 0.01 MergeLinearizations48TxWorstCase
11,300.76 88,489.66 0.1% 0.01 MergeLinearizations64TxWorstCase
17,576.48 56,894.21 0.0% 0.01 MergeLinearizations75TxWorstCase
31,100.42 32,153.91 0.0% 0.01 MergeLinearizations99TxWorstCase
278.14 3,595,258.27 0.0% 0.01 PostLinearize16TxWorstCase
863.81 1,157,659.26 0.0% 0.01 PostLinearize32TxWorstCase
2,657.66 376,271.23 0.2% 0.01 PostLinearize48TxWorstCase
4,652.11 214,956.22 0.0% 0.01 PostLinearize64TxWorstCase
5,392.31 185,449.25 0.2% 0.01 PostLinearize75TxWorstCase
9,431.09 106,032.27 0.1% 0.01 PostLinearize99TxWorstCase

src/cluster_linearize.h Show resolved Hide resolved
src/test/fuzz/cluster_linearize.cpp Show resolved Hide resolved
sipa added 5 commits August 1, 2024 14:07
This makes it clearer what the function does.
Add utility functions to DepGraph for finding connected components.
When the transactions being marked done exactly match the first chunk of
what remains of the linearization, we can just remember to skip that
chunk instead of computing a full rechunking.

Further, chop off prefixes of the input linearization that are already done,
so they don't need to be reconsidered for further rechunkings.
@sipa sipa force-pushed the 202406_clusterlin_meta branch from 157464f to bbcee5a Compare August 1, 2024 20:13
Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code review ACK bbcee5a

// During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
// groups [2] and [3,0,1].

std::vector<TxEntry> entries(linearization.size() + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we persisting entries between passes just so we don't need to reallocate this vector? I don't see that we need to keep any of the information from a previous pass. To check, added a entries.clear() which didn't seem to break anything for me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the only reuse is the vector allocation.

*
* Postlinearization guarantees:
* - The resulting chunks are connected.
* - If the input has a tree shape (either all transactions have at most one child, or all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a subset of the "there exists a maximum of 1 path from any node to another" definition of a tree, right? This definition seems to be like a... botanical tree?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to https://en.wikipedia.org/wiki/Tree_(graph_theory), a transaction graph where each transaction has at most one child would be an arborescence or out-tree, and the opposite an anti-arborescence or in-tree.

src/test/fuzz/cluster_linearize.cpp Show resolved Hide resolved
@DrahtBot DrahtBot requested review from sdaftuar and instagibbs August 2, 2024 11:14
Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK bbcee5a

// - Each direction corresponds to one shape of tree being linearized optimally (forward passes
// guarantee this for graphs where each transaction has at most one child; backward passes
// guarantee this for graphs where each transaction has at most one parent).
// - Starting with a backward pass guarantees the moved-tree property.
Copy link
Member

@instagibbs instagibbs Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved-leaf? (or point to another usage of moved tree)

@sdaftuar
Copy link
Member

sdaftuar commented Aug 2, 2024

ACK bbcee5a

@glozow glozow merged commit bba01ba into bitcoin:master Aug 5, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants