You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
--exclude-tests over Rust is quadratic in depth × attributed-item count when
each attributed item carries a run of one attribute. PR #1446 fixed the
sibling shape (one long run, many attributes) by reusing a verdict across the
run; runs of length 1 have nothing to reuse, so this axis is untouched.
Roughly half of the cost predates #1446's attribute arm: it is rust_outer_attr_marks_test restarting at the parent's first child once per item, i.e. the #1100 budget design rather than any recent regression.
Reproduction
D nested fns, then 3*D separate one-attribute items at the innermost
level:
~4× per doubling in all three columns that grow, so the class is quadratic with
and without the attribute arm.
Root cause
forward_attribute_scan_budget is max(6, 3 * depth)
(big-code-analysis-ast/src/checker.rs). At depth D it permits a width-3D
parent onto the forward reading, and rust_attribute_run_under then walks the
parent's children from index 0 for every item it is asked about. With 3D
items under one parent that is O(D²).
The budget scales with depth because a previous_sibling climb costs O(depth)
while a cursor step is flat (#1100's measurements, recorded in the constants'
doc comments). That reasoning is sound per call; what it does not price is
being called O(width) times on the same parent.
Fix sketch
The shape of #1446's fix does not transfer directly — its reach works because
every attribute in a run shares one answer, and here each item has its own.
Options:
Reach on the other axis. A verdict for item i could report that it
answers for the run boundary it already walked past, letting the next item
start where the last stopped. Needs the walker to carry a per-parent cursor
rather than a single reach, which is more state than fix(metrics/loc): six LOC fixes from the 2026-09-10 residue #1446 added.
Cap the forward budget by absolute width, not just depth. A parent wider
than some constant takes the backward reading regardless of depth. Cheap, but
it re-opens the O(depth²) case perf(checker): the exclude_tests attribute scan is O(attributes x depth) #1100 closed for deep-and-narrow trees, so it
needs that fixture re-measured.
Memoize per parent. One (parent_id, run_verdict) slot on the walk would
collapse the repeat asks. Adds walk state and a cache-invalidation question.
Measure before choosing: the shape is pathological, and real Rust is a few
percent attributed. This is filed because the class is real and now named, not
because it has been seen in practice.
Found while fixing the #1446 review's HIGH finding, by checking whether the fix
covered the adjacent diagonal. It does not, and the new nom/deep-attribute-run
probe does not cover it either.
Summary
--exclude-testsover Rust is quadratic in depth × attributed-item count wheneach attributed item carries a run of one attribute. PR #1446 fixed the
sibling shape (one long run, many attributes) by reusing a verdict across the
run; runs of length 1 have nothing to reuse, so this axis is untouched.
Roughly half of the cost predates #1446's attribute arm: it is
rust_outer_attr_marks_testrestarting at the parent's first child once peritem, i.e. the #1100 budget design rather than any recent regression.
Reproduction
Dnestedfns, then3*Dseparate one-attribute items at the innermostlevel:
$ bca metrics --no-config --jobs 1 --metrics nom --exclude-tests -p dN.rs--exclude-tests~4× per doubling in all three columns that grow, so the class is quadratic with
and without the attribute arm.
Root cause
forward_attribute_scan_budgetismax(6, 3 * depth)(
big-code-analysis-ast/src/checker.rs). At depth D it permits a width-3Dparent onto the forward reading, and
rust_attribute_run_underthen walks theparent's children from index 0 for every item it is asked about. With
3Ditems under one parent that is
O(D²).The budget scales with depth because a
previous_siblingclimb costsO(depth)while a cursor step is flat (#1100's measurements, recorded in the constants'
doc comments). That reasoning is sound per call; what it does not price is
being called
O(width)times on the same parent.Fix sketch
The shape of #1446's fix does not transfer directly — its reach works because
every attribute in a run shares one answer, and here each item has its own.
Options:
answers for the run boundary it already walked past, letting the next item
start where the last stopped. Needs the walker to carry a per-parent cursor
rather than a single reach, which is more state than fix(metrics/loc): six LOC fixes from the 2026-09-10 residue #1446 added.
than some constant takes the backward reading regardless of depth. Cheap, but
it re-opens the
O(depth²)case perf(checker): the exclude_tests attribute scan is O(attributes x depth) #1100 closed for deep-and-narrow trees, so itneeds that fixture re-measured.
(parent_id, run_verdict)slot on the walk wouldcollapse the repeat asks. Adds walk state and a cache-invalidation question.
Measure before choosing: the shape is pathological, and real Rust is a few
percent attributed. This is filed because the class is real and now named, not
because it has been seen in practice.
Found while fixing the #1446 review's HIGH finding, by checking whether the fix
covered the adjacent diagonal. It does not, and the new
nom/deep-attribute-runprobe does not cover it either.