Struct CostSink

Struct Documentation

struct CostSink

Opt-in accumulator for the replay-tallied (recompute-aware) cost.

A static per-node cost walk reports order-/batching-blind DP-model quantities: each internal node is priced once, so such a walk never sees the per-occ-block replay recompute the batched evaluator incurs at runtime. This sink is the replay-side counterpart: when a non-null CostSink is attached to the CostModel shared by every dry-run Result token, each actual product-op execution during the Trace::On replay folds its own sliced-extent cost here (see CostModel::tally_op and DryRunOps::prod). Because a sliced, occ-dependent op executed N times does ~1/N work each pass, its sliced-cost sum is work-neutral (~= its unsliced cost); only the occ-independent work re-executed at full size once per block inflates — so the totals here isolate the recompute the model walk cannot.

Mirrors sequant::eval::PeakSink (eval.hpp): an optional sink, defaulting off, so the production runtime path (which never constructs a dry-run CostModel) folds nothing. The atomics let a fold from a concurrent evaluator stay correct, though a metered replay itself is single-threaded.

Per-node avoidable-recompute tally, keyed by the label signature (result + operand indices). Avoidable recompute is measured in FLOPs against the batching-free (unlimited-memory) ideal, where each distinct value is built once at full extent and reused: total_flops accumulates the actual (possibly sliced) FLOPs over every build of this value; full_flops is the FLOPs to build it once at full extent (constant per label). The rollup takes avoidable = max(0, total_flops - full_flops) — the arithmetic batching repeats beyond building the value once, which is exactly the recompute hoisting exists to avoid.

FLOPs (unlike roofline exec) is linear in extents, hence additive across slices: disjoint slices that tile the full value sum to exactly full_flops => 0 avoidable (tiling repeats no arithmetic), while a value rebuilt full once per block sums to N*full => (N-1)*full avoidable. That additivity is why no slice-context bucketing is needed and why the pathological >100% roofline-spread of the exec-weighted metric cannot arise.

Note: the per-distinct-value avoidable rollup does not live here. It is kept by CacheManager::recompute_tally(), keyed by the exact cache node identity (TreeNodeHasher + TreeNodeEqualityComparator) so 64-bit hash collisions are not folded; a string-keyed sink here could not reproduce that identity (the node type is kept out of this header by the dryrun/eval_expr.hpp -> cost_model_object.hpp include cycle). This sink carries only the whole-forest scalar totals (flops/exec/n_ops via tally_op).

Public Members

std::atomic<double> flops = {0.0}
std::atomic<double> exec = {0.0}
std::atomic<std::size_t> n_ops = {0}