Skip to content

perf: fix top 5 high-performance-go.md violations found by codebase audit - #762

Merged
jeduden merged 8 commits into
mainfrom
claude/kind-darwin-pgr733
Jul 25, 2026
Merged

perf: fix top 5 high-performance-go.md violations found by codebase audit#762
jeduden merged 8 commits into
mainfrom
claude/kind-darwin-pgr733

Conversation

@jeduden

@jeduden jeduden commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

A codebase-wide audit against docs/development/high-performance-go.md was run using a fleet of agents scanning internal/rules/**, the core parse/lint pipeline (internal/lint, internal/mdtext, internal/engine, pkg/markdown, pkg/goldmark), and the schema/config/discovery surfaces. Most of the codebase turned out to already be heavily tuned against this exact doc (many files carry inline comments citing prior alloc-budget work), which narrowed the field considerably. Every candidate below was independently verified by reading the actual code and, where the claim was about CPU/allocation cost, by writing a real benchmark before touching anything — one high-severity candidate (a claimed doublestar.Match revalidation cost in internal/discovery) did not survive that check and was dropped.

The five fixes below are each backed by a red/green test that fails against the old code and passes against the new code, per this repo's TDD workflow. The PR then went through three rounds of adversarial xhigh-effort code review: round 1 found and fixed a real regression (see below), round 2 found and fixed a minor test-robustness issue, round 3 came back clean and recommended merge.

Fixes

  1. internal/lint: closure-box allocation on RunCache/File.Memo warm pathFile.Memo, File.MemoFile, and RunCache's shared load() helper each paid 2 allocations per cache-hit call: sync.Map.LoadOrStore's second argument (a throwaway &memoEntry{}/&runCacheEntry{}) is constructed and discarded on every hit, and sync.Once.Do(func(){ e.val = build() }) allocates its argument closure regardless of whether Do ends up invoking it. These are called at least once per host file across a workspace run. Fixed with a Load-before-LoadOrStore check plus the atomic.Bool+mutex double-checked-lock pattern (no wrapping closure) — verified at 0 allocs/op on the warm path (was 2).

  2. MDS021 (include): unconditional per-file alloc + mutex lockCheck/Fix allocated a visited map and chain slice and took a rule-wide sync.Mutex (serializing this default-enabled rule's Check across the engine's parallel per-file fan-out) before ever checking whether the file has an <?include?> directive. Gated behind a cheap byte-needle pre-check, matching the mayContainURL pattern already used by nobareurls. (Round 1 of review caught that the first version of this gate only checked for "<?include", silently dropping the "unexpected generated section end marker" diagnostic for a file with a dangling <?/include?> and no opening marker — fixed by checking both the start and end needles.)

  3. MDS027 (cross-file-reference-integrity): redundant per-file glob revalidationCheck called validateGlobSettings() on every file, re-validating the same static Include/Exclude glob lists that ApplySettings already validated once at config-load time. Cached behind cachedGlobSettingsErr using the same atomic.Bool+mutex pattern as fix Add heading-max, code-block-max, and stern mode to line-length rule #1; ApplySettings resets the cache so reconfiguration (or a test that sets fields directly, bypassing ApplySettings) still gets a fresh, correct verdict.

  4. MDS043 (no-reference-style): ungated footnote regex scancheckFootnotes ran two full-file regex passes unconditionally on every Check, even for files with no footnote syntax at all. Benchmarked at ~585µs/op on 28KB of plain prose, ~0.5µs/op once gated behind a mayContainFootnote byte-needle check (both regexes require "[^" to match anything).

  5. internal/foreignregion: O(regions × lines) scanScan walked the whole file once per configured region, re-converting every line via strings.TrimSpace(string(line)) each time. Restructured into a single pass over f.Lines that evaluates every region's marker state against one shared bytes.TrimSpace per line, and switched to []byte/bytes.Equal instead of allocating a string per line per region.

Motivation

mdsmith check runs its rule set over every file in a workspace; the project's own performance doc states the budget philosophy plainly: "one extra alloc per Check means tens of thousands per run." Fixes #1–4 hit default-enabled or otherwise commonly-invoked code paths, so their savings apply broadly across a workspace check rather than to one rule in isolation.

Test plan

  • Each fix has a dedicated test that fails on the pre-fix code and passes after (allocation-count assertions via testing.AllocsPerRun, or a benchmark with an inline budget for the one CPU-bound fix)
  • go build ./...
  • go vet ./...
  • gofmt -l . — clean
  • go test ./... (full suite, including -race on touched packages)
  • go tool -modfile=tools/go.mod golangci-lint run ./... — 0 issues
  • go run ./cmd/mdsmith check . — 563 files checked, 0 failures
  • Three rounds of adversarial xhigh-effort code review (1 real bug found and fixed, 1 minor test-robustness fix, 1 clean pass)
  • All 31 CI checks green

claude added 5 commits July 21, 2026 20:42
File.Memo/MemoFile and RunCache's shared load() helper both paid two
allocations on every cache-hit call: LoadOrStore's second argument
(a throwaway &memoEntry{}/&runCacheEntry{}) is constructed before the
call and discarded whenever the key already exists, and
sync.Once.Do(func(){ e.val = build() }) allocates the wrapping
closure as an argument even when Do's internal check makes it a
no-op. These accessors run at least once per host file across a
workspace check, so the per-call cost multiplied by the corpus size.

Switch both to a Load-before-LoadOrStore check plus the
atomic.Bool+mutex double-checked-lock pattern (no wrapping closure),
bringing the warm path to zero allocations.

Ref: docs/development/high-performance-go.md ("Reuse loop-local
buffers", "sync.Pool for transient state").
Check/Fix unconditionally allocated a fresh visited map and chain
slice, and took the rule-wide mutex serialising this default-enabled
rule's Check calls across the engine's per-file parallel fan-out,
before ever checking whether the file could contain an <?include?>
directive. MDS021 runs on every host file in a workspace, so this
cost (and the lost parallelism) applied even to files that never use
the directive.

Add a cheap bytes.Contains(f.Source, "<?include") pre-check so a file
with no include marker returns immediately without allocating or
locking, matching the "gate expensive analyzers behind a cheap
pre-check" pattern already used elsewhere (e.g. nobareurls'
mayContainURL).

Ref: docs/development/high-performance-go.md.
…er file

Check called validateGlobSettings() on every file — re-running
doublestar validation over the rule's Include/Exclude glob lists
even though ApplySettings already validated the identical, static
list once when config was applied. MDS027 is default-enabled, so
every host file in a workspace paid this redundant revalidation.

Cache the verdict behind cachedGlobSettingsErr, using the same
atomic.Bool+mutex pattern as the RunCache/Memo fix so the warm path
costs one atomic load instead of a closure allocation. ApplySettings
resets the cache before recomputing so a rule instance that gets
reconfigured (or a test that bypasses ApplySettings entirely, setting
Include/Exclude directly) still sees a correct, freshly validated
result on the next Check.

The struct grew by 24 bytes (error + mutex + atomic.Bool); the
layout test's expected size is updated accordingly with the packing
math for why 144 (not more) is optimal.

Ref: docs/development/high-performance-go.md ("Memoize per-input
computations").
checkFootnotes ran two full-file regex passes (footnoteRefRE and
footnoteDefRE, each a FindAllSubmatchIndex over the whole source) on
every Check call, even for files with no footnote-style syntax at
all. Benchmarked at ~585µs/op on a 28KB prose file with no footnotes,
versus ~0.8µs/op once gated — both regexes require the literal bytes
"[^" to match anything.

Add mayContainFootnote, mirroring nobareurls' mayContainURL, and
return early from checkFootnotes when the source can't contain either
pattern. A benchmark with an inline budget (b.Fatalf at 50µs/op, two
orders of magnitude above the gated cost) pins the regression so CI
catches a dropped gate.

Ref: docs/development/high-performance-go.md ("Gate expensive
analyzers behind a cheap pre-check").
Scan called scanOne once per declared foreign-region marker pair, and
each call re-walked the entire f.Lines slice, converting every line
via strings.TrimSpace(string(line)) — an O(regions x lines) cost with
one string allocation per line per region, even though every region's
markers can be checked against the same per-line trim in one pass.

Restructure Scan to walk f.Lines once, computing bytes.TrimSpace(line)
a single time per line and evaluating it against every region's
regionScanState. This also drops the per-line string() conversion in
favor of staying in []byte with bytes.Equal comparisons. A test pins
that 5 regions costs close to what 1 region costs (a small per-region
constant), not 5x.

Ref: docs/development/high-performance-go.md ("Stay in []byte",
"Skip work you don't need").
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.70%. Comparing base (d4af5d5) to head (d67a633).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
Components Coverage Δ
Go 98.69% <100.00%> (+<0.01%) ⬆️
TypeScript 99.54% <ø> (ø)

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

claude added 3 commits July 21, 2026 20:48
TestCheck_NoFS and TestCheck_NoIncludeDirectiveAllocatesNothing pinned
these branches for Check, but Fix shares the same compound early-return
condition and had no equivalent coverage — Codecov flagged the gate
added in c2db2fa as only 50% patch-covered. Add the mirroring Fix
tests.
The needle gate added in c2db2fa only checked for "<?include", which
does not match a dangling "<?/include?>" end marker with no opening
start marker ("/" sits where the needle expects "i"). Such a file
would false-negative past the gate, silently dropping the engine's
"unexpected generated section end marker" diagnostic that Check
previously emitted — a regression a user would hit by deleting an
opening <?include ... ?> block but leaving the closing marker behind.

Check for both "<?include" and "<?/include" via
mayContainIncludeDirective. Caught by round 1 of adversarial code
review.
TestCheck_NoIncludeDirectiveAllocatesNothing asserts a zero-alloc
delta, but the race detector's instrumentation adds its own
allocations, so the test fails under `go test -race` even though the
gate itself is correct — CI does not run -race, so this only bites a
developer running it locally. Add the raceEnabled build-tag const
pattern already used by internal/rules/nobareurls and
internal/rules/crossfilereferenceintegrity, and skip the gate under
it. Noted in round 2 of code review.
@jeduden
jeduden marked this pull request as ready for review July 21, 2026 21:15
Copilot AI review requested due to automatic review settings July 21, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jeduden jeduden added queue Add to a PR to enqueue it queue:active Applied automatically when a PR is in an active batch and removed queue Add to a PR to enqueue it labels Jul 25, 2026
@jeduden

jeduden commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

🟢 Merge Queue — picked up

This PR is in the queue and will be batched with other queue-labelled PRs.

Next: No action needed — you'll get another comment when CI starts on the batch. View merge queue run.

@jeduden

jeduden commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

🔵 Merge Queue — CI running

Merged into batch branch merge-queue/batch-754-1785006212 alongside #754, #756, #758, #764. View CI run.

Next: No action needed — you'll be notified when CI completes.

@jeduden
jeduden merged commit a08e800 into main Jul 25, 2026
31 checks passed
@jeduden jeduden removed the queue:active Applied automatically when a PR is in an active batch label Jul 25, 2026
@jeduden

jeduden commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Merge Queue — merged

This PR landed on main via commit 47ace27. CI run that validated the merge.

Next: Done — nothing more to do here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants