Merge bitcoin/bitcoin#29757: feefrac: avoid explicitly computing diagram; compare based on chunks

b22901dfa9 Avoid explicitly computing diagram; compare based on chunks (Pieter Wuille)

Pull request description:

  This merges the `BuildDiagramFromChunks` and `CompareFeeRateDiagram` introduced in #29242 into a single `CompareChunks` function, which operates on sorted chunk data rather than diagrams, instead computing the diagram on the fly.

  This avoids the need for the construction of an intermediary diagram object, and removes the slightly arbitrary "all diagrams must start at (0, 0)" requirement.

  Not a big deal, but I think the result is a bit cleaner and not really more complicated.

ACKs for top commit:
  glozow:
    reACK b22901d
  instagibbs:
    reACK b22901dfa9

Tree-SHA512: ca37bdf61d9a9cb5435f4da73e97ead33bf65828ad9af49b87336b1ece70db8ced1c21f517fc6eb6d616311c91f3da75ecae6b9bd42547133e3a3c5320b7816d
This commit is contained in:
glozow
2024-04-24 16:39:54 +01:00
9 changed files with 142 additions and 192 deletions

View File

@@ -7,39 +7,26 @@
#include <array>
#include <vector>
std::vector<FeeFrac> BuildDiagramFromChunks(const Span<const FeeFrac> chunks)
{
std::vector<FeeFrac> diagram;
diagram.reserve(chunks.size() + 1);
diagram.emplace_back(0, 0);
for (auto& chunk : chunks) {
diagram.emplace_back(diagram.back() + chunk);
}
return diagram;
}
std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1)
std::partial_ordering CompareChunks(Span<const FeeFrac> chunks0, Span<const FeeFrac> chunks1)
{
/** Array to allow indexed access to input diagrams. */
const std::array<Span<const FeeFrac>, 2> dias = {dia0, dia1};
const std::array<Span<const FeeFrac>, 2> chunk = {chunks0, chunks1};
/** How many elements we have processed in each input. */
size_t next_index[2] = {1, 1};
size_t next_index[2] = {0, 0};
/** Accumulated fee/sizes in diagrams, up to next_index[i] - 1. */
FeeFrac accum[2];
/** Whether the corresponding input is strictly better than the other at least in one place. */
bool better_somewhere[2] = {false, false};
/** Get the first unprocessed point in diagram number dia. */
const auto next_point = [&](int dia) { return dias[dia][next_index[dia]]; };
const auto next_point = [&](int dia) { return chunk[dia][next_index[dia]] + accum[dia]; };
/** Get the last processed point in diagram number dia. */
const auto prev_point = [&](int dia) { return dias[dia][next_index[dia] - 1]; };
// Diagrams should be non-empty, and first elements zero in size and fee
Assert(!dia0.empty() && !dia1.empty());
Assert(prev_point(0).IsEmpty());
Assert(prev_point(1).IsEmpty());
const auto prev_point = [&](int dia) { return accum[dia]; };
/** Move to the next point in diagram number dia. */
const auto advance = [&](int dia) { accum[dia] += chunk[dia][next_index[dia]++]; };
do {
bool done_0 = next_index[0] == dias[0].size();
bool done_1 = next_index[1] == dias[1].size();
bool done_0 = next_index[0] == chunk[0].size();
bool done_1 = next_index[1] == chunk[1].size();
if (done_0 && done_1) break;
// Determine which diagram has the first unprocessed point. If a single side is finished, use the
@@ -69,17 +56,16 @@ std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const
// If B and P have the same size, B can be marked as processed (in addition to P, see
// below), as we've already performed a comparison at this size.
if (point_b.size == point_p.size) ++next_index[!unproc_side];
if (point_b.size == point_p.size) advance(!unproc_side);
}
// If P lies above AB, unproc_side is better in P. If P lies below AB, then !unproc_side is
// better in P.
if (std::is_gt(cmp)) better_somewhere[unproc_side] = true;
if (std::is_lt(cmp)) better_somewhere[!unproc_side] = true;
++next_index[unproc_side];
advance(unproc_side);
// If both diagrams are better somewhere, they are incomparable.
if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered;
} while(true);
// Otherwise compare the better_somewhere values.

View File

@@ -146,15 +146,14 @@ struct FeeFrac
}
};
/** Takes the pre-computed and topologically-valid chunks and generates a fee diagram which starts at FeeFrac of (0, 0) */
std::vector<FeeFrac> BuildDiagramFromChunks(Span<const FeeFrac> chunks);
/** Compares two feerate diagrams. The shorter one is implicitly
* extended with a horizontal straight line.
/** Compare the feerate diagrams implied by the provided sorted chunks data.
*
* A feerate diagram consists of a list of (fee, size) points with the property that size
* is strictly increasing and that the first entry is (0, 0).
* The implied diagram for each starts at (0, 0), then contains for each chunk the cumulative fee
* and size up to that chunk, and then extends infinitely to the right with a horizontal line.
*
* The caller must guarantee that the sum of the FeeFracs in either of the chunks' data set do not
* overflow (so sum fees < 2^63, and sum sizes < 2^31).
*/
std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1);
std::partial_ordering CompareChunks(Span<const FeeFrac> chunks0, Span<const FeeFrac> chunks1);
#endif // BITCOIN_UTIL_FEEFRAC_H