]> git.hungrycats.org Git - bees/commitdiff
bees: make BeesRewritePolicy defaults invalid sentinels
authorZygo Blaxell <bees@furryterror.org>
Sun, 5 Jul 2026 16:42:14 +0000 (12:42 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:04:13 +0000 (00:04 -0400)
BeesRewritePolicy predates the invalid-sentinel convention (ref_cost and
extent_cost already used UINT64_MAX; refs_max was added as a sentinel).
Retrofit the remaining config-loaded fields so that a policy which bypasses
BeesConfig::load_rewrite_policy() fails loudly instead of running on
plausible-looking defaults.

The C++ member initializers for dedupe/copy/hole/total-max, compressed-
dedupe-max, candidate-max-count/-bytes, free-min-ratio, restart-max,
insert-sample-max, plan-max-vertices, read-size (0) and the four booleans
(false) are now sentinels; the authoritative defaults remain in
bees-config-v2.cc, which load_rewrite_policy() writes into every field.  A
struct-level comment documents the convention.

plan-max-vertices loses its former real default (16384): directly-constructed
policies now set it explicitly, so the "kept real for tests" carve-out is gone.

The two bare constructors are updated to supply their own values:
  - test-bees-plan.cc default_policy() sets all 21 fields to the surveyed
    config defaults, so every test using it behaves exactly as before;
  - bench-bees-plan.cc sets plan-max-vertices (a 0 sentinel would force the
    greedy path and defeat the DP profiler); its stale build comment is
    corrected to require --gc-sections and to list the op-limit arguments.

No behavior change: production policies are fully populated by the loader.

Assisted-by: Claude-Code:claude-opus-4-8
src/bees.h
test/bench-bees-plan.cc
test/test-bees-plan.cc

index e9a2ae7ce25ab06d8e68305c4a846efc1d6ae35b..4713614cbac654bbfc81c4db5d16abdf05f52354 100644 (file)
@@ -1364,19 +1364,27 @@ struct BeesResolveAddrResult {
 };
 
 /// Rewrite-planner limits derived from the [rewrite] config section.
+///
+/// Every field is loaded by BeesConfig::load_rewrite_policy(); the authoritative
+/// defaults live in bees-config-v2.cc.  The C++ member initializers below are
+/// deliberately invalid sentinels (0 / false, or UINT64_MAX where 0 is a valid
+/// value) so a policy that bypasses the loader fails loudly — rejecting
+/// everything or mis-costing obviously — instead of running on plausible
+/// defaults.  Directly-constructed policies in tests and benchmarks must set the
+/// fields they exercise (see test/test-bees-plan.cc default_policy()).
 struct BeesRewritePolicy {
-       uint64_t m_dedupe_max = 100;
+       uint64_t m_dedupe_max = 0;
        /// Maximum number of dedupe operations allowed against a single
        /// compressed destination extent.  A compressed extent can be
        /// physically minimal yet hold many logical blocks, so a plan can
        /// pile many dedupe ops onto it, exploding ref count (~100 bytes
        /// per ref) for almost no physical space gain.  Allow at most 2
        /// (covers two compressed files with identical content but
-       /// mismatched extent boundaries); reject more.  Default 2.
-       uint64_t m_compressed_dedupe_max = 2;
-       uint64_t m_copy_max = 100;
-       uint64_t m_hole_max = 100;
-       uint64_t m_total_max = 100;
+       /// mismatched extent boundaries); reject more.  Config default 2.
+       uint64_t m_compressed_dedupe_max = 0;
+       uint64_t m_copy_max = 0;
+       uint64_t m_hole_max = 0;
+       uint64_t m_total_max = 0;
        /// Maximum references a single extent may accumulate before bees stops
        /// adding more (rewrite.refs-max; default 9999, clamped to the ioctl hard
        /// ceiling BEES_MAX_EXTENT_REF_COUNT, which "unlimited" resolves to).
@@ -1386,35 +1394,35 @@ struct BeesRewritePolicy {
        /// Soft cap that prevents runaway plans driven by many tiny
        /// extents.  Paired with m_candidate_max_bytes (a soft cap on
        /// total candidate bytes); whichever ceiling is hit first stops
-       /// candidate accumulation.  Default 1024.
-       uint64_t m_candidate_max_count = 1024;
+       /// candidate accumulation.  Config default 1024.
+       uint64_t m_candidate_max_count = 0;
        /// Maximum total logical bytes across all candidate extents.
        /// Soft cap that prevents runaway plans driven by a few huge
-       /// extents.  Paired with m_candidate_max_count.  Default: 512 MB
+       /// extents.  Paired with m_candidate_max_count.  Config default 512 MB
        /// (4× max extent length).
-       uint64_t m_candidate_max_bytes = 512ULL * 1024 * 1024;
+       uint64_t m_candidate_max_bytes = 0;
        /// Minimum net bytes a rewrite must free — good_blocks minus
        /// bad_blocks, in bytes.  A rewrite is accepted only if
        /// (good - bad) * block_size >= max(m_free_min_bytes,
        /// m_free_min_ratio * total_bytes).  Exactly one of the two
-       /// forms is set by the config parser: a percentage sets the
-       /// ratio, a byte-count sets the absolute floor.
+       /// forms is set by the config parser (which zeroes both first): a
+       /// percentage sets the ratio, a byte-count sets the absolute floor.
        uint64_t m_free_min_bytes = 0;
-       double m_free_min_ratio = 0.5;
+       double m_free_min_ratio = 0.0;
        /// Maximum hash-conflict restarts per extent.
-       uint64_t m_restart_max = 4;
+       uint64_t m_restart_max = 0;
        /// Maximum hashes inserted per extent during scan (MinHash
-       /// fingerprint size).  Default 100 (~0.3% sample at 128 MB).
-       uint64_t m_insert_sample_max = 100;
+       /// fingerprint size).  Config default 100 (~0.3% sample at 128 MB).
+       uint64_t m_insert_sample_max = 0;
        /// Check for hash insert collisions during scan.
-       bool m_insert_conflict_check = true;
-       /// Match algorithm selection.  When true (default), use
-       /// anchor-extend matching; otherwise greedy-forward.
-       bool m_match_anchor_extend = true;
+       bool m_insert_conflict_check = false;
+       /// Match algorithm selection.  When true, use anchor-extend
+       /// matching; otherwise greedy-forward.  Config default true.
+       bool m_match_anchor_extend = false;
        /// Lock inodes of all extent refs before planning.
-       bool m_inode_locking = true;
+       bool m_inode_locking = false;
        /// Build Plan A (start extent as dedupe dst).  When true
-       /// (default), scan_next_plan creates a BeesStartAsDstPlan that
+       /// (config default), scan_next_plan creates a BeesStartAsDstPlan that
        /// looks for src candidates whose hashes match the start extent
        /// and dedupes matching ranges into the start.  When false,
        /// Plan A still runs for hole-punch and unreachable-block
@@ -1424,9 +1432,9 @@ struct BeesRewritePolicy {
        /// older copy in this scan pass.  Conceptually: "keep older
        /// copies" — when an older indexed extent has matching content,
        /// the newer start extent is replaced by a reference to it.
-       bool m_keep_older = true;
+       bool m_keep_older = false;
        /// Build Plan B (start extent as dedupe src).  When true
-       /// (default), scan_next_plan creates a BeesStartAsSrcPlan that
+       /// (config default), scan_next_plan creates a BeesStartAsSrcPlan that
        /// looks for dst candidates whose hashes match the start
        /// extent and dedupes the start's content into matching
        /// extents.  When false, Plan B is suppressed entirely; the
@@ -1435,7 +1443,7 @@ struct BeesRewritePolicy {
        /// Conceptually: "keep newer copies" — when an older indexed
        /// extent matches the newly-scanned start, the older copy is
        /// replaced by a reference to start.
-       bool m_keep_newer = true;
+       bool m_keep_newer = false;
        /// Per-ref space-debt weight, in bytes, used by the do-nothing
        /// cost model (see docs/config-file.md and do-nothing-plan.md §4).
        /// Each ref a plan adds to the filesystem costs this many bytes of
@@ -1459,17 +1467,17 @@ struct BeesRewritePolicy {
        /// can stall a worker and every task queued behind that extent.  When
        /// V exceeds this budget the planner abandons the exact search for a
        /// linear-time greedy cover and stops gathering further candidates.
-       /// See docs/config-file.md; a real default is kept here (not a loud
-       /// sentinel) so directly-constructed policies in tests/benchmarks get
-       /// the exact DP for small inputs.  `unlimited` disables the fallback.
-       uint64_t m_plan_max_vertices = 16384;
+       /// See docs/config-file.md.  Config default 16384; `unlimited` disables
+       /// the fallback.  Tests/benchmarks that exercise the search must set this
+       /// (0 forces the greedy path immediately).
+       uint64_t m_plan_max_vertices = 0;
        /// Buffer size for source-side reads in the materialize copy
        /// loop and the block_map_fetch hashing pass.  Larger values
        /// amortise pread syscall and ref-selection cost over more bytes;
        /// smaller values reduce per-thread RSS.  Should be a multiple of
        /// the filesystem sectorsize; non-multiples just waste a sliver
-       /// of the last block in each chunk.  Default: 1 MiB.
-       uint64_t m_read_size = 1024 * 1024;
+       /// of the last block in each chunk.  Config default 1 MiB.
+       uint64_t m_read_size = 0;
 };
 
 /**
index 6809ae9d92d31ff606a1350f855abdce39cc1af3..969d18e9a69b642b607541182d3b02edcec6c1fc 100644 (file)
@@ -5,10 +5,18 @@
 //
 // Build (from test/):
 //   make test-bees-plan-lib.o
-//   clang++ $(BEES_CXXFLAGS) -I../src -o bench-bees-plan \
+//   clang++ $(BEES_CXXFLAGS) -I../src -ffunction-sections -fdata-sections \
+//       -Wl,--gc-sections -o bench-bees-plan \
 //       bench-bees-plan.cc test-bees-plan-lib.o -L../lib -lcrucible -lpthread -latomic
+// (--gc-sections is required: test-bees-plan-lib.o is all of bees-plan.cc,
+//  which contains functions this bench never calls — CostReport's operator<<
+//  and BeesPlan::print (needing pretty(double) and BeesExtent::bytenr) and
+//  BeesCandidateQueue::pick_best_candidate (needing BeesExtent::phys_size) —
+//  that reference symbols this standalone link does not provide.  The section
+//  GC drops them because nothing reachable from main() calls them.)
 //
-// Usage: ./bench-bees-plan [num_matches] [region_blocks] [max_match_blocks] [seed]
+// Usage: ./bench-bees-plan [num_matches] [region_blocks] [max_match_blocks] \
+//                          [seed] [per-type-max] [total-max]
 
 #include "bees-plan.h"
 
@@ -60,6 +68,10 @@ main(int argc, char **argv)
        policy.m_copy_max   = PerType;
        policy.m_hole_max   = PerType;
        policy.m_total_max  = TotalMax;
+       // BeesRewritePolicy's C++ default is now an invalid sentinel (0), which
+       // would force the greedy path immediately, so set the DP budget explicitly.
+       // Config default 16384; raise it to profile the exact DP at higher V.
+       policy.m_plan_max_vertices = 16384;
 
        const auto t0 = chrono::steady_clock::now();
        const auto res = scan_next_plan_shortest_path(in, policy);
index d159b838fad6371baeae33581d9714a7bce786ea..5ae9d31fffeb67059b69524627510f09fdc44d00 100644 (file)
@@ -31,12 +31,32 @@ using namespace std;
 static BeesRewritePolicy
 default_policy()
 {
+       // BeesRewritePolicy's C++ initializers are invalid sentinels (a loader
+       // bypass must fail loudly), so a directly-constructed policy has to set
+       // every field it exercises.  Use the surveyed config defaults from
+       // bees-config-v2.cc; individual tests override specific limits locally.
        BeesRewritePolicy p;
-       p.ref_cost = 53;
-       p.extent_cost = 53;
-       // Keep the rewrite limits at their struct defaults (all 100, except
-       // compressed-dedupe-max = 2); tests that need specific limits set
-       // them locally.
+       p.m_dedupe_max            = 100;
+       p.m_compressed_dedupe_max = 2;
+       p.m_copy_max              = 100;
+       p.m_hole_max              = 100;
+       p.m_total_max             = 100;
+       p.m_refs_max              = 9999;
+       p.m_candidate_max_count   = 1024;
+       p.m_candidate_max_bytes   = 512ULL * 1024 * 1024;
+       p.m_free_min_bytes        = 0;
+       p.m_free_min_ratio        = 0.5;
+       p.m_restart_max           = 4;
+       p.m_insert_sample_max     = 100;
+       p.m_insert_conflict_check = true;
+       p.m_match_anchor_extend   = true;
+       p.m_inode_locking         = true;
+       p.m_keep_older            = true;
+       p.m_keep_newer            = true;
+       p.ref_cost                = 53;
+       p.extent_cost             = 53;
+       p.m_plan_max_vertices     = 16384;
+       p.m_read_size             = 1024 * 1024;
        return p;
 }