Skip to content

perf: audit against high-performance-go.md, fix 5 measured hot paths - #774

Draft
jeduden wants to merge 5 commits into
mainfrom
claude/kind-darwin-3fs54p
Draft

perf: audit against high-performance-go.md, fix 5 measured hot paths#774
jeduden wants to merge 5 commits into
mainfrom
claude/kind-darwin-3fs54p

Conversation

@jeduden

@jeduden jeduden commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Audited the Go core against docs/development/high-performance-go.md using a fleet of scanning agents (internal/rules/**, internal/lint/internal/mdtext/pkg/markdown/pkg/goldmark/internal/engine, and cmd/mdsmith/internal/lsp/internal/discovery/internal/index) plus targeted before/after benchmarking of each candidate (per the doc's own "profile before you rewrite" methodology).

This repo has already been through several rounds of this exact audit (#762, #764, #765, #767), and — while scanning — I found #770 (branch claude/kind-darwin-v8cn6a) already open with a fifth, independent round covering HeadingText/HeadingTextBase memoization, schema.ExtractDocHeadings memoization, Slugify presizing, a goldmark bulk-copy fix, and a disabled-logger allocation. This PR deliberately does not touch any of that ground — all five fixes below are distinct hot paths #770 doesn't cover, so the two PRs should merge cleanly independent of order.

Five issues survived measurement and are fixed here, each its own commit with a dedicated before/after benchmark:

  1. internal/discoverymatchesAny re-validated already-validated patterns. validatePatterns filters w.patterns to the valid set once up front, but matchesAny then called doublestar.Match, which re-runs doublestar.ValidatePattern's parse walk on every file × pattern comparison during the workspace walk. internal/globpath already fixed this exact shape for the config-driven glob surfaces (its own comment cites "~4% of check CPU"); this mirrors it for file discovery, which runs on every check/fix/LSP-index invocation over every file in the tree. BenchmarkMatchesAny: ~780 ns/op → ~685 ns/op (0 allocs/op both ways).
  2. cmd/mdsmithorderFilesLeavesFirst built its dependency index serially. The loader (bytelimit.ReadFileLimited keyed by a read-only map, each call opening its own file handle) is safe for concurrent use, but the call used BuildSerial instead of the parallel Build that internal/index ships specifically for this shape (its own benchmarks show ~2x on a 1k-file corpus). Every mdsmith fix run paid the fully serial cost before the fix pass even starts. BenchmarkOrderFilesLeavesFirst (500 files): ~30 ms → ~26 ms.
  3. catalog (MDS-catalog) — numeric sort re-parsed every entry on every comparison. sortEntries' numeric-mode comparator called parseSortInt (a CUE-path tokenize + map walk + strconv.ParseInt) on every comparison — O(n log n) re-parses — even though allParseAsInt already parses every entry once just to decide whether numeric mode applies. The parsed int64 is now cached alongside the sort's existing lowercase-key precompute. BenchmarkSortEntriesNumeric (5000 entries): ~23.4 ms / 307,798 allocs → ~3.9 ms / 29,806 allocs.
  4. pkg/goldmark/astBaseNode's childCount sat between pointer fields. BaseNode declared a scalar (childCount) between its Node pointer fields and attributes ([]Attribute, pointer-ish), extending GC ptrdata over it for nothing. Every other struct-layout test in the package skips index 0 (the embedded base), so BaseNode's own layout had never been checked — despite being embedded in every concrete AST node type parsed. No unsafe code depends on the field order (verified via grep).
  5. astutil.SectionBody scanned every paragraph on every call. MDS057 (required-text-patterns) and MDS058 (required-mentions) each call SectionBody once per heading over the same paragraph slice, making a single Check O(headings × paragraphs). Both headings and paragraphs are already sorted by source line, so sort.Search now bounds the matching range directly instead of a full scan. BenchmarkSectionBodyPerHeading (500 headings × 5 paragraphs): ~1.44 ms / 1500 allocs → ~0.12 ms / 1000 allocs.

Test plan

  • go build ./...
  • go test ./... (full suite green, 161 packages)
  • go vet ./...
  • go test -race on every touched package (discovery, cmd/mdsmith, catalog, goldmark/ast, astutil, index)
  • go tool -modfile=tools/go.mod golangci-lint run on changed packages (0 issues)
  • internal/integration per-rule alloc/timing budget gates and the rule-walk-audit manifest pass unchanged (MDS057/MDS058's budgets specifically re-verified after the SectionBody change)
  • Each fix has a dedicated benchmark confirmed slow against the pre-fix code, faster after (shown in each commit message)
  • mdsmith check . on the repo (567 files, 0 failures)

Co-Authored-By: Claude Sonnet 5


Generated by Claude Code

claude added 5 commits July 26, 2026 20:29
matchesAny called doublestar.Match, which re-runs
doublestar.ValidatePattern's parse walk on every file x pattern
comparison during the workspace walk, even though validatePatterns
already filters w.patterns to the valid set up front. Per
docs/development/high-performance-go.md's "skip work you don't need",
internal/globpath already fixed this exact shape for the config-driven
glob surfaces; this mirrors it for file discovery, which runs on every
check/fix/lsp invocation over every file in the tree.

BenchmarkMatchesAny: ~780 ns/op -> ~685 ns/op (0 allocs/op both ways).
orderFilesLeavesFirst called index.(*Index).BuildSerial even though
its loader (bytelimit.ReadFileLimited keyed by a read-only relToAbs
map, each call opening its own file handle) is safe for concurrent
use. index.(*Index).Build fans the same extractor across
runtime.GOMAXPROCS(0) workers for exactly this shape of workload
(internal/index's own Build-vs-BuildSerial benchmarks show ~2x on a
1k-file synthetic corpus); this call site paid the fully serial cost
on every `mdsmith fix` run over the workspace before the fix pass even
starts.

BenchmarkOrderFilesLeavesFirst (500 files): ~30ms -> ~26ms.
sortEntries' numeric-sort comparator called parseSortInt (a
fieldinterp.ParseCUEPath tokenize plus a map walk plus
strconv.ParseInt) on every comparison, i.e. O(n log n) times, even
though allParseAsInt already parses every entry once just to decide
whether numeric mode applies. Per
docs/development/high-performance-go.md's "memoize per-input
computations" (the same pattern the string-sort path already applies
to its lowercase keys), the parsed int64 is now cached alongside the
existing sortKey/tiebreakerKey precompute and reused in the
comparator.

BenchmarkSortEntriesNumeric (5000 entries): ~23.4ms/307798 allocs ->
~3.9ms/29806 allocs.
BaseNode declared childCount (a scalar) between its Node pointer
fields and attributes ([]Attribute, pointer-ish), extending the
struct's GC ptrdata over a field that carries no pointer. Per
docs/development/high-performance-go.md's "group pointer fields first,
scalars last", every other struct-layout test in this package
(TestAutoLink_PointerFieldsBeforeScalars et al.) checks this rule via
fieldorder.AssertPointersBeforeScalars, but all of them skip index 0
to exclude the embedded BaseNode itself, so its own layout had never
been checked. BaseNode is embedded in BaseBlock and BaseInline, i.e.
in every concrete AST node type parsed, making it the
highest-multiplicity struct in the parser.

No unsafe code depends on the field order (confirmed via grep across
pkg/goldmark); reordering only changes GC scan cost per node, not the
struct's size.
SectionBody linearly scanned the entire paragraph slice on every
call, filtering by line range. MDS057 (required-text-patterns) and
MDS058 (required-mentions) both call it once per heading over the
same paragraph slice, turning a single Check into
O(headings * paragraphs) instead of O(headings * log paragraphs).
Both headings and paragraphs are already sorted by source line
(CollectSectionParagraphs walks the AST in document order), so per
docs/development/high-performance-go.md's "sorted slice + binary
search" pattern, sort.Search now bounds the matching range directly
instead of scanning every paragraph on every call.

BenchmarkSectionBodyPerHeading (500 headings x 5 paragraphs each):
~1.44ms/1500 allocs -> ~0.12ms/1000 allocs.
@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.71%. Comparing base (2ab4b29) to head (bea6b02).

Additional details and impacted files
Components Coverage Δ
Go 98.70% <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.

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.

2 participants