return c.space_debt(policy);
}
+// Above this many boundary vertices (V), the exact O(V^2) shortest-path DP is
+// abandoned for a linear-time greedy minimal-interval cover. test/bench-bees-
+// plan.cc puts the DP's worst-case knee (collapse on) at V ~ 8192-16384;
+// beyond it a single dense extent can occupy a worker — and, because it holds
+// that dst's Exclusion the whole time, starve every task queued behind it —
+// for seconds to minutes (production has seen an 8-hour single-extent grind
+// with the collapse fast path disabled). The greedy minimizes dedupe-op count
+// (hence fragment count and refs_added / debt) but ignores dedupe-vs-copy debt
+// tradeoffs, so it returns a valid, limit-satisfying, but possibly-suboptimal
+// cover in O(M log M + V).
+//
+// PROTOTYPE: the threshold is a file-scope constant here; productionizing it
+// means a rewrite-policy key (e.g. rewrite.plan-max-vertices) wired across
+// bees.h / bees-config-v2.cc / bees-context.cc / docs/config-file.md per the
+// "update all docs when adding a config option" rule.
+static constexpr size_t BEES_PLAN_V_BUDGET = 8192;
+
+// Total boundary-vertex count across all data regions — the DP's actual cost
+// driver V (region ends plus distinct in-region match endpoints). Cheap
+// (O(R*M + V log V)); gates the greedy fallback. Mirrors the per-region vertex
+// construction in the DP below so the count matches the walk exactly.
+static size_t
+scan_next_plan_boundary_count(const PlanSearchInputs &in)
+{
+ size_t total = 0;
+ for (const auto ®ion : in.m_data_regions) {
+ vector<uint64_t> verts{ region.m_begin, region.m_end };
+ for (const auto &m : in.m_matches) {
+ if (m.m_dst_end <= region.m_begin
+ || m.m_dst_begin >= region.m_end) continue;
+ if (m.m_dst_begin > region.m_begin
+ && m.m_dst_begin < region.m_end) verts.push_back(m.m_dst_begin);
+ if (m.m_dst_end > region.m_begin
+ && m.m_dst_end < region.m_end) verts.push_back(m.m_dst_end);
+ }
+ sort(verts.begin(), verts.end());
+ verts.erase(unique(verts.begin(), verts.end()), verts.end());
+ total += verts.size();
+ }
+ return total;
+}
+
+// Greedy fallback for high-V extents: a maximal-reach minimal-interval cover.
+// Walks each data region left to right; at each position it extends by the
+// match reaching farthest from at-or-before that position (which minimizes the
+// number of dedupe ops, hence fragments and refs_added), and fills any
+// uncovered gap with one copy slice. Op counts accumulate across regions (F is
+// whole-extent); a copy run never spans the hole between regions. Returns
+// nullopt when the resulting cover cannot satisfy the rewrite limits (mirroring
+// the DP's "every path pruned" outcome) or when a compressed dst has a gap no
+// dedupe covers (copy edges are not permitted on a compressed dst). The debt
+// is computed by scan_next_covering_debt, identical to the DP's terminal
+// evaluation, so the caller's acceptable()/executor path is unchanged.
+static optional<PlanSearchResult>
+scan_next_plan_greedy(const PlanSearchInputs &in,
+ const BeesRewritePolicy &policy)
+{
+ // block_count matching the DP (ceil division; spans are block-aligned in
+ // practice so this equals exact division, but stay identical to the DP).
+ const auto block_count = [&](uint64_t begin, uint64_t end) -> size_t {
+ return static_cast<size_t>(
+ (end - begin + in.m_sums_block_size - 1) / in.m_sums_block_size);
+ };
+
+ // Matches in ascending dst_begin order for the reach sweep.
+ vector<size_t> by_begin(in.m_matches.size());
+ for (size_t i = 0; i < by_begin.size(); ++i) by_begin[i] = i;
+ sort(by_begin.begin(), by_begin.end(), [&](size_t x, size_t y) {
+ return in.m_matches[x].m_dst_begin < in.m_matches[y].m_dst_begin;
+ });
+
+ PlanSearchResult result;
+ size_t dedupe_ops = 0;
+ size_t copy_ops = 0;
+ size_t matched_blocks = 0;
+
+ for (const auto ®ion : in.m_data_regions) {
+ uint64_t pos = region.m_begin;
+ size_t mp = 0; // sweep cursor into by_begin
+ uint64_t best_end = region.m_begin; // running max clamped reach
+ size_t best_idx = in.m_matches.size();
+ while (pos < region.m_end) {
+ // Absorb matches now eligible (dst_begin <= pos); keep the running
+ // argmax of clamped dst_end. Monotone across the region -> O(M+V).
+ while (mp < by_begin.size()
+ && in.m_matches[by_begin[mp]].m_dst_begin <= pos) {
+ const size_t idx = by_begin[mp];
+ const auto &m = in.m_matches[idx];
+ ++mp;
+ if (m.m_dst_end <= region.m_begin
+ || m.m_dst_begin >= region.m_end) continue;
+ const uint64_t end = min(m.m_dst_end, region.m_end);
+ if (end > best_end) {
+ best_end = end;
+ best_idx = idx;
+ }
+ }
+ if (best_end > pos) {
+ // Dedupe edge [pos, best_end) served by match best_idx.
+ result.m_selected_matches.push_back(best_idx);
+ matched_blocks += block_count(pos, best_end);
+ ++dedupe_ops;
+ pos = best_end;
+ } else {
+ // Gap: no match covers pos. A compressed dst forbids copies.
+ if (in.m_dst_compressed) return nullopt;
+ uint64_t gap_end = region.m_end;
+ if (mp < by_begin.size()) {
+ const uint64_t nb = in.m_matches[by_begin[mp]].m_dst_begin;
+ if (nb > pos && nb < gap_end) gap_end = nb;
+ }
+ result.m_copy_slices.push_back(
+ PlanSearchRegion{ pos, gap_end });
+ ++copy_ops;
+ pos = gap_end;
+ }
+ }
+ }
+
+ // Same limits the DP enforces during the walk (scan_next_rewrite_acceptable
+ // remains the sole final authority; this only keeps the fallback honest).
+ if (dedupe_ops > policy.m_dedupe_max) return nullopt;
+ if (copy_ops > policy.m_copy_max) return nullopt;
+ if (dedupe_ops + copy_ops + in.m_hole_ops > policy.m_total_max) return nullopt;
+
+ result.m_matched_blocks = matched_blocks;
+ result.m_debt = scan_next_covering_debt(in, policy,
+ dedupe_ops, copy_ops, matched_blocks);
+ return result;
+}
+
optional<PlanSearchResult>
scan_next_plan_shortest_path(const PlanSearchInputs &in,
const BeesRewritePolicy &policy)
return nullopt;
}
+ // V-budget: the exact DP below is O(V^2) in the boundary count and holds
+ // the dst Exclusion for its whole duration, so a high-V extent can wedge a
+ // worker (and everything queued behind that extent). Above the budget,
+ // abandon the exact search for the linear greedy cover.
+ if (scan_next_plan_boundary_count(in) > BEES_PLAN_V_BUDGET) {
+ return scan_next_plan_greedy(in, policy);
+ }
+
// Per-edge ref debit shared by every dedupe and copy edge.
const double K = static_cast<double>(in.m_dst_ref_count)
* static_cast<double>(policy.ref_cost);