Summary
Two follow-ups the #1062 hasher work identified but deliberately left out of
scope. Both are the same shape as that change: self-produced integer keys
paying for hash-flooding resistance they cannot use, plus an allocation that
does not need to happen.
1. loc's line-number sets still use SipHash
src/metrics/loc.rs:169,241,249 hold HashSet<usize> keyed by line
numbers, constructed with HashSet::default() at :258-259 and hit on
every line inserted, unioned or extended. The argument from NodeIdHasher
(src/spaces.rs) applies verbatim — self-produced integers, never
attacker-chosen — and FxHash is a particularly good fit here: consecutive
line numbers times an odd multiplier is a bijection on the low bits, so
distribution is ideal.
Loc is default-selected and an MI input, so this is a hotter path than the
cognitive map that motivated the hasher.
2. NestingMap has no capacity reserve
src/spaces/compute.rs builds NestingMap::default() fresh per file with no
with_capacity_and_hasher. A 50 000-node file rehashes roughly 14 times
mid-walk. Either reserve from a node-count estimate, or hoist one map across
files and clear() it — clear keeps the allocation, which is the point.
This was flagged as plausibly worth more than the hash-function swap itself,
and it is cheaper to write.
Measure, do not assume
The hasher change taught this the hard way: the first numbers quoted for it
were unsupportable, and the corpus was misdescribed. Anything done here
should come with an interleaved before/after and a control metric, and
ideally wait for the harness in #1068 so the numbers are reproducible rather
than living in a commit message.
Resolution
Status: Fixed (pending merge)
Commit: 9b33344 on branch fix/issue-1069
Both sub-parts landed, both measured with an interleaved paired
protocol and controls (see the fix-summary comment for the full table):
- loc line sets —
Ploc/Cloc now key on IntKeyHasher
(NodeIdHasher moved to src/int_hash.rs and renamed, since it is no
longer node-id specific). corpus/walk/loc -7.1% (19/20 rounds,
sign-test p < 0.0001), shape/walk/loc/nested-while -8.7% (19/20,
p < 0.0001).
- nesting map reserve — sized from
Node::descendant_count() when
Cognitive is selected and the grammar's impl actually seeds slots
(new Cognitive::SEEDS_NESTING, false for the Preproc /
Ccomment no-ops so their map stays unallocated).
shape/walk/cognitive/nested-while -12.2% (17/20, p = 0.0026).
Not resolvable on the corpus slice (median file ~5 KiB): two runs
read -4.4% (p = 0.057) and +0.2% (p = 1.0). Reported as unresolved,
not as a number.
Controls (corpus/walk/tokens, shape/walk/tokens/nested-paren,
shape/walk/nom/nested-while) sat at +0.7% to +6.6% — cross-build code
layout, which bounds the precision at roughly +/-7 points and is why
the direction and the round-by-round consistency, not the magnitude,
are the claim.
The "Measure, do not assume" requirement also produced a doc change:
the sequential --save-baseline / --baseline recipe in
docs/development/benchmarking.md reported 23% and 19% swings on
benchmarks the change cannot reach, so that doc now carries the
interleaved protocol used here.
Behaviour is bit-identical — no metric value or snapshot moved.
Summary
Two follow-ups the #1062 hasher work identified but deliberately left out of
scope. Both are the same shape as that change: self-produced integer keys
paying for hash-flooding resistance they cannot use, plus an allocation that
does not need to happen.
1.
loc's line-number sets still use SipHashsrc/metrics/loc.rs:169,241,249holdHashSet<usize>keyed by linenumbers, constructed with
HashSet::default()at:258-259and hit onevery line inserted, unioned or extended. The argument from
NodeIdHasher(
src/spaces.rs) applies verbatim — self-produced integers, neverattacker-chosen — and FxHash is a particularly good fit here: consecutive
line numbers times an odd multiplier is a bijection on the low bits, so
distribution is ideal.
Locis default-selected and an MI input, so this is a hotter path than thecognitive map that motivated the hasher.
2.
NestingMaphas no capacity reservesrc/spaces/compute.rsbuildsNestingMap::default()fresh per file with nowith_capacity_and_hasher. A 50 000-node file rehashes roughly 14 timesmid-walk. Either reserve from a node-count estimate, or hoist one map across
files and
clear()it —clearkeeps the allocation, which is the point.This was flagged as plausibly worth more than the hash-function swap itself,
and it is cheaper to write.
Measure, do not assume
The hasher change taught this the hard way: the first numbers quoted for it
were unsupportable, and the corpus was misdescribed. Anything done here
should come with an interleaved before/after and a control metric, and
ideally wait for the harness in #1068 so the numbers are reproducible rather
than living in a commit message.
Resolution
Status: Fixed (pending merge)
Commit: 9b33344 on branch
fix/issue-1069Both sub-parts landed, both measured with an interleaved paired
protocol and controls (see the fix-summary comment for the full table):
Ploc/Clocnow key onIntKeyHasher(
NodeIdHashermoved tosrc/int_hash.rsand renamed, since it is nolonger node-id specific).
corpus/walk/loc-7.1% (19/20 rounds,sign-test p < 0.0001),
shape/walk/loc/nested-while-8.7% (19/20,p < 0.0001).
Node::descendant_count()whenCognitiveis selected and the grammar's impl actually seeds slots(new
Cognitive::SEEDS_NESTING,falsefor thePreproc/Ccommentno-ops so their map stays unallocated).shape/walk/cognitive/nested-while-12.2% (17/20, p = 0.0026).Not resolvable on the corpus slice (median file ~5 KiB): two runs
read -4.4% (p = 0.057) and +0.2% (p = 1.0). Reported as unresolved,
not as a number.
Controls (
corpus/walk/tokens,shape/walk/tokens/nested-paren,shape/walk/nom/nested-while) sat at +0.7% to +6.6% — cross-build codelayout, which bounds the precision at roughly +/-7 points and is why
the direction and the round-by-round consistency, not the magnitude,
are the claim.
The "Measure, do not assume" requirement also produced a doc change:
the sequential
--save-baseline/--baselinerecipe indocs/development/benchmarking.mdreported 23% and 19% swings onbenchmarks the change cannot reach, so that doc now carries the
interleaved protocol used here.
Behaviour is bit-identical — no metric value or snapshot moved.