perf: fix top 5 high-performance-go.md violations (struct layout + byte scans) - #765
Conversation
SplitLines built its result via append in a byte-by-byte scan for '\n', growing the slice through several capacity doublings and never vectorizing the scan. bytes.Split uses the same SIMD-accelerated IndexByte the rest of the codebase already relies on and pre-sizes the result in one pass. On a 50-line representative body this drops 6 allocs/op to 1; on a 2000-line stress input, 13 allocs/186us to 1 alloc/43us. Per docs/development/high-performance-go.md "Strings and bytes": bytes.IndexByte over a hand-rolled byte loop. This is a regression of a pattern the project already fixed once (internal/lsp/diagnostics.go splitLines uses bytes.Split for the same reason). Adds TestSplitLines_AllocBudget and BenchmarkSplitLines pinning the 1-alloc budget so a regression fails CI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
… loop documentEndPosition's trailing-newline branch counted '\n' with a scalar per-byte Go for-range loop. bytes.Count uses the same SIMD-accelerated scan bytes.IndexByte relies on, and this function runs on every LSP "fix all" whole-document code action (fullFileEdit) over the complete document buffer. On a representative 2000-line document this drops ~63us/op to ~1.6-2us/op with allocations unchanged at zero. Per docs/development/high-performance-go.md "Strings and bytes": bytes.IndexByte (and its relatives, including bytes.Count) over a hand-rolled byte loop for scanning a large Source. Adds BenchmarkDocumentEndPosition, pinning the zero-alloc property so a regression fails CI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
internal/lsp.Diagnostic placed Range (a scalar struct) and Severity (an int) before Code/Source/Message/Data/RelatedInformation/ CodeDescription. Go's GC ptrdata for a struct spans from offset 0 through the end of the last pointer-containing field, so those two scalars sat inside the scanned span for no reason. This is the type's own doc comment's "keystroke hot path" — built once per diagnostic on every publishDiagnostics call — and internal/lint.Diagnostic already carries the same fix with the same rationale. Per docs/development/high-performance-go.md "Struct layout": order fields large-to-small, group pointer fields first and scalars last. Adds TestDiagnostic_PointerFieldsPrecedeScalars using the project's existing internal/structlayout.AssertPointerFieldsFirst helper, the same pattern already used for lint.Diagnostic, schema.ScopeMatch, and others. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
… scalars pkg/mdsmith.Diagnostic interleaved Line/Column/SourceStartLine/ Deprecated (scalars) among its string/slice/pointer fields; its sibling RelatedLocation interleaved Line/Column before Message. Neither got the pointer-fields-first fix that internal/lint.Diagnostic and internal/lint.RelatedLocation already carry for the same reason: Go's GC ptrdata for a struct spans from offset 0 through the last pointer-containing field, so a scalar declared before that point sits inside the scanned span for nothing. toDiagnostics/toRelatedLocations build one of these per lint finding on every Session.Check/Session.Fix call — the public engine API that the WASM bindings, the Obsidian plugin, and any other embedder of pkg/mdsmith go through. Per docs/development/high-performance-go.md "Struct layout": order fields large-to-small, group pointer fields first and scalars last. Adds structlayout_test.go with the same structlayout.AssertPointerFieldsFirst pattern already used for lint.Diagnostic, schema.ScopeMatch, and others. JSON field tags are unchanged, so the wire shape (CLI --format json / LSP / WASM host) is unaffected by the in-memory reorder. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
… before scalars internal/output.jsonDiagnostic interleaved Line/Column/ SourceStartLine/Deprecated (scalars) among its string/slice/pointer fields; jsonRelatedLocation interleaved Line/Column before Message. Same defect and same fix as internal/lint.Diagnostic and pkg/mdsmith.Diagnostic: Go's GC ptrdata for a struct spans from offset 0 through the last pointer-containing field, so a scalar declared before that point sits inside the scanned span for nothing. One of these is built per diagnostic on every `--format json` run (CI pipelines, `mdsmith check --format json`). Per docs/development/high-performance-go.md "Struct layout": order fields large-to-small, group pointer fields first and scalars last. Adds structlayout_test.go with the project's existing structlayout.AssertPointerFieldsFirst pattern. Reordering the Go struct fields changes encoding/json's emitted key order (Go encodes struct fields in declaration order), so TestJSONFormatter_ExactOutput is updated to match; JSON key order carries no semantic meaning and no reference doc commits to a specific order, and the CLI's own e2e JSON tests parse into a map rather than asserting exact text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The struct-layout fix in internal/output.jsonDiagnostic (previous commit) reordered Go struct fields to group pointer-containing fields before scalars, which changes encoding/json's emitted key order. This reference page's --format json example still showed the pre-fix order. Verified against actual `mdsmith check --format json` output. Found by an independent adversarial review pass on PR #765. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
|
🟢 Merge Queue — picked up This PR is in the queue and will be batched with other Next: No action needed — you'll get another comment when CI starts on the batch. View merge queue run. |
|
🔵 Merge Queue — CI running Merged into batch branch Next: No action needed — you'll be notified when CI completes. |
|
✅ Merge Queue — merged This PR landed on Next: Done — nothing more to do here. |
Summary
An audit against
docs/development/high-performance-go.md(four parallel scans covering allocation anti-patterns, the "patterns to avoid" table, struct layout/data structures, and strings/bytes/concurrency) surfaced several real violations. This PR fixes the top 5, ranked by hot-path frequency, safety, and testability:internal/lsp.Diagnostic—Range/Severity(scalars) were declared before pointer-containing fields (Code,Source,Message,Data,RelatedInformation,CodeDescription). Go's GC computes a struct's ptrdata as the span from offset 0 through the last pointer-containing field, so those scalars sat inside the scanned span for nothing. This type's own doc comment calls it out as the "keystroke hot path" — built once per diagnostic on everypublishDiagnosticscall.pkg/mdsmith.Diagnostic/RelatedLocation— same interleaving defect on the public Session API surface (WASM bindings, Obsidian plugin, any embedder), built once per diagnostic intoDiagnostics/toRelatedLocations. The siblinginternal/lint.Diagnosticalready carries this exact fix; these two were missed.internal/output.jsonDiagnostic/jsonRelatedLocation— same defect on the CI /--format jsonoutput path, built once per diagnostic on everymdsmith check --format jsonrun.gensection.SplitLines— hand-rolled byte-by-byte scan for'\n'building the result via unsizedappend, instead ofbytes.Split. This is a regression of a pattern the project already fixed once (internal/lsp/diagnostics.go'ssplitLinesusesbytes.Splitfor the same reason). Runs on everyEngine.Fixpass for<?include?>/<?catalog?>-style directives. Measured: 6 allocs/op → 1 alloc/op on a representative 50-line body; 13 allocs/186µs → 1 alloc/43µs on a 2000-line stress input.documentEndPosition— manual per-byte newline-counting loop instead ofbytes.Count, on every LSP "fix all" whole-document code action. Measured: ~63µs/op → ~1.6–2µs/op on a representative 2000-line document, allocations unchanged at zero.All five map directly to sections of
docs/development/high-performance-go.md: "Struct layout" (group pointer fields first, scalars last — GC ptrdata spans through the last pointer field) for 1–3, and "Strings and bytes" (bytes.IndexByte/bytes.Countover a hand-rolled byte loop) for 4–5.Approach
Each fix follows red/green TDD:
structlayout_test.goper package using the project's existinginternal/structlayout.AssertPointerFieldsFirstreflection helper (already used forlint.Diagnostic,schema.ScopeMatch, and others) — confirmed red against the current field order, then reordered fields to green.testing.AllocsPerRun,b.Fatalfon overshoot, matching theBenchmarkRule_MDS024convention) — confirmed red against the manual loop, then swapped in the stdlib call to green.Note: reordering
jsonDiagnostic's Go struct fields changesencoding/json's emitted key order (Go encodes in declaration order), soTestJSONFormatter_ExactOutputis updated to match. JSON key order carries no semantic meaning, no reference doc commits to a specific order, and the CLI's own e2e JSON tests parse into a map rather than asserting exact text — only that one pinned unit test needed updating.Test plan
go build ./...go test ./...(full suite green)go vet ./...go tool -modfile=tools/go.mod golangci-lint run ./...(0 issues)internal/integrationallocation-budget gate passesgo run ./cmd/mdsmith check .(0 failures)structlayout_test.go/ alloc-budget tests confirmed red against the pre-fix code, green after🤖 Generated with Claude Code
Generated by Claude Code