BeesExtent m_candidate;
std::vector<ExtentMatch> m_matches;
CostReport m_entry_cost;
+ /// This candidate is a whole-extent duplicate of the start (one dedupe
+ /// op, no copy/hole), so it could replace the start as the star's
+ /// center — see finalize().
+ bool m_whole_extent = false;
};
std::deque<Entry> m_entries;
+ /// Set by finalize() (prefer-canonical-src) when a whole-extent candidate
+ /// outranks the start on reference count: the plan re-centers the dedupe
+ /// star on that candidate, the start joins the destinations, and every
+ /// destination dedupes onto this center instead of the start. Empty means
+ /// the start is the center (the ordinary Plan B).
+ std::optional<BeesExtent> m_pivot_center;
};
BeesStartAsSrcPlan::BeesStartAsSrcPlan(const BeesExtent &start,
Impl::Entry entry;
entry.m_candidate = candidate;
entry.m_matches = matches->m_candidate_as_dst;
+ // A single dedupe op covering the whole extent (no copy or hole) means the
+ // candidate is a whole-extent duplicate of the start — eligible to become
+ // the star's center in finalize() (prefer-canonical-src).
+ entry.m_whole_extent =
+ chosen->rewrite_ops() == 1 && chosen->dedupe_ops() == 1;
entry.m_entry_cost.m_bytes_freed =
static_cast<uint64_t>(chosen->good_blocks())
* static_cast<uint64_t>(BLOCK_SIZE_SUMS);
// continues(), so dropping the stop does not affect termination.
}
+void
+BeesStartAsSrcPlan::finalize()
+{
+ // prefer-canonical-src: if a whole-extent-duplicate candidate holds more
+ // references than the start, re-center the dedupe star on it. Centering on
+ // the highest-reference member minimizes the references the plan moves
+ // (every other member's references move onto the center), and for a set of
+ // equivalent whole-extent duplicates that reference churn is the only cost
+ // that differs between orientations. The center is a single argmax over the
+ // accepted entries — O(N), no pairwise search.
+ if (!m_policy.m_prefer_canonical_src || m_impl->m_entries.empty()) {
+ return;
+ }
+ auto &layer = Borrower::current().layer();
+ const uint64_t start_refs = m_start.refs(layer)->size();
+ const Impl::Entry *center = nullptr;
+ uint64_t center_refs = start_refs;
+ for (const auto &e : m_impl->m_entries) {
+ if (!e.m_whole_extent) {
+ continue;
+ }
+ // Whole-extent entry => F == 1 => m_refs_created is the candidate's
+ // own reference count.
+ const uint64_t refs = e.m_entry_cost.m_refs_created;
+ if (refs > center_refs) {
+ center_refs = refs;
+ center = &e;
+ }
+ }
+ if (!center) {
+ // The start already holds the most references — ordinary Plan B.
+ return;
+ }
+ m_impl->m_pivot_center = center->m_candidate;
+ // Re-centering swaps the start and the center between the dst set and the
+ // src role. The members are whole-extent duplicates, so they free equal
+ // physical space and the extent delta is unchanged; only the moved-
+ // reference count changes, dropping by (center_refs - start_refs) — the
+ // churn the pivot saves. Adjust the aggregate debt and re-price.
+ m_cost.m_refs_created = m_cost.m_refs_created - center_refs + start_refs;
+ m_cost.m_space_debt = m_cost.space_debt(m_policy);
+}
+
list<BeesExtent>
BeesStartAsSrcPlan::dsts() const
{
list<BeesExtent> rv;
- for (const auto &e : m_impl->m_entries) {
- rv.push_back(e.m_candidate);
+ if (m_impl->m_pivot_center) {
+ // Re-centered: the start is now a dst, the center is the src.
+ const auto center_bytenr = m_impl->m_pivot_center->bytenr();
+ rv.push_back(m_start);
+ for (const auto &e : m_impl->m_entries) {
+ if (e.m_candidate.bytenr() == center_bytenr) {
+ continue;
+ }
+ rv.push_back(e.m_candidate);
+ }
+ } else {
+ for (const auto &e : m_impl->m_entries) {
+ rv.push_back(e.m_candidate);
+ }
}
return rv;
}
list<BeesExtent>
BeesStartAsSrcPlan::srcs() const
{
- // The start extent is the sole src; return it only when the
- // plan has accepted at least one dst.
+ // Return the src only when the plan has accepted at least one dst. The src
+ // is the start, unless finalize() re-centered the star on a higher-ref
+ // whole-extent candidate (prefer-canonical-src), in which case the start is
+ // a dst and that candidate is the sole src.
if (m_impl->m_entries.empty()) {
return {};
}
+ if (m_impl->m_pivot_center) {
+ return { *m_impl->m_pivot_center };
+ }
return { m_start };
}
}
const auto &start = pb->start();
+ // prefer-canonical-src re-centered the star on a higher-ref whole-extent
+ // duplicate (finalize()): the center is the sole src; the start and the
+ // remaining candidates are the dsts. The center is byte-identical to
+ // the start over the whole extent, so a dst's match against the start
+ // transposes onto the center by swapping the src extent, and the start's
+ // own dst is a whole-extent match. The center is a real src (push-to-
+ // front hash refresh via m_src_bytenrs, like a Plan A src); the start
+ // does not survive.
+ if (impl->m_pivot_center) {
+ const auto ¢er = *impl->m_pivot_center;
+ const auto center_bytenr = center.bytenr();
+ out.m_src_bytenrs.insert(center_bytenr);
+ out.m_start_survived = false;
+ const auto build_onto_center = [&](const BeesExtent &dst,
+ vector<ExtentMatch> matches) {
+ for (auto &m : matches) {
+ m.m_src = center;
+ }
+ vector<CandidateGroup> sources;
+ CandidateGroup g;
+ g.m_extent = center;
+ g.m_matches = std::move(matches);
+ sources.push_back(std::move(g));
+ const bool force = filter_cache.force_rewrite(dst.bytenr());
+ try {
+ auto dp = scan_next_try_build_dst_plan(ctx, dst,
+ sources, policy, force);
+ if (dp) {
+ dp->m_space_debt = winner_space_debt;
+ dp->m_sel_refs_created = winner_refs_created;
+ dp->m_dst_ref_count = dst.refs(diag_layer)->size();
+ out.m_total_cost += dp->m_cost;
+ out.m_dst_plans.push_back(std::move(*dp));
+ }
+ } catch (const Borrower::Dead &) {
+ throw;
+ } catch (const exception &e) {
+ scan_next_log_skip_extent("plan-b-pivot-build", dst, e);
+ }
+ };
+ // The start, deduped whole-extent onto the center.
+ const auto start_ls = start.logical_size(diag_layer);
+ ExtentMatch sm;
+ sm.m_dst = start;
+ sm.m_dst_begin = 0;
+ sm.m_dst_end = start_ls;
+ sm.m_src = center;
+ sm.m_src_begin = 0;
+ sm.m_src_end = start_ls;
+ build_onto_center(start, { sm });
+ // The remaining candidates, transposed onto the center.
+ for (const auto &entry : impl->m_entries) {
+ if (entry.m_candidate.bytenr() == center_bytenr) {
+ continue;
+ }
+ build_onto_center(entry.m_candidate, entry.m_matches);
+ }
+ return out;
+ }
+
for (const auto &entry : impl->m_entries) {
vector<CandidateGroup> sources;
CandidateGroup g;