Skip to content

perf(scan): parse/read files once, parallelize sections, scope cache#24

Merged
alxxjohn merged 1 commit into
mainfrom
go-cleanup
Jul 2, 2026
Merged

perf(scan): parse/read files once, parallelize sections, scope cache#24
alxxjohn merged 1 commit into
mainfrom
go-cleanup

Conversation

@alxxjohn

@alxxjohn alxxjohn commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Cuts redundant work in the scan engine and fixes a latent clone-cache correctness bug. From a performance audit of the scan hot path.

  • Walk/read/parse each file once per scan — new shared fileCorpus (runner/support/corpus.go) memoizes the per-target walk, file reads, and Go AST parses. quality and design now reuse one AST via support.ParseGoSource instead of re-parsing the same file. Taint/graph parsers keep their cheaper parse modes and are left untouched.
  • Parallel sections — enabled sections run on a NumCPU-bounded worker pool with deterministic, position-indexed result ordering. ScanCache and ArtifactStore are now mutex-guarded and OnSectionComplete is serialized. Section-level (not file-level) parallelism was chosen deliberately: some evaluators mutate closure state and are only safe within a serial section.
  • Per-section cache fingerprints — the per-file cache key is now scoped to the config family that actually affects a section's findings (cache version bumped 6→7). Editing an unrelated field (output format, target names, excludes) no longer invalidates the whole cache. Unknown section IDs fall back to an all-checks hash, so a new section can never silently serve stale entries.
  • Clone detection O(C²) → bucketed — candidates are partitioned by (LeftDoc, RightDoc) so the overlap merge scans only that file-pair's bucket; document paths are slash-normalized once instead of per comparator call.
  • Regex memoization — dynamically compiled TypeScript security regexes are cached in a sync.Map and reused across files.

Bug fixed

Clone detection built its document list via a closure side-effect and returned nil, but ScanTargetFiles skips the evaluator on a cache hit — so all quality.duplicate-code findings silently disappeared on any second scan with a warm cache (confirmed against the original code: cold=6 clones, warm=0). Fixed by routing clone doc-collection through VisitTargetFiles, which bypasses the findings cache.

Verification

  • go build / go vet / gofmt clean; golangci-lint: 0 issues (CI gate).
  • Full test suite passes with -race — no data races in the new concurrent path.
  • Cold and warm scans are byte-identical (deterministic output).
  • Interleaved benchmarks: ~1.2× faster on ~500 files, ~1.7× faster on ~1,400 files. Tradeoff: higher peak memory (~61 MB vs ~4 MB) from the retained corpus.
  • Added one waiver for unbounded-goroutines-in-loop on the worker pool (true false positive — goroutines are semaphore-bounded) to keep the repo's own CI self-scan clean.

Notes

Also updated .claude/knowledge/architecture-boundaries.md with the pure-evaluator cache constraint, the new concurrency model, and the per-section fingerprint scheme.

🤖 Generated with Claude Code

Cuts redundant work in the scan engine and fixes a latent clone-cache bug.

- Walk, read, and Go-parse each file once per scan via a shared fileCorpus
  (runner/support/corpus.go); quality and design reuse one AST through
  support.ParseGoSource instead of re-parsing.
- Run enabled sections on a NumCPU-bounded worker pool with deterministic,
  position-indexed result ordering. ScanCache and ArtifactStore are now
  mutex-guarded and OnSectionComplete is serialized.
- Scope the per-file cache fingerprint per config family (cache v7) so
  unrelated config edits (output format, target names, excludes) no longer
  invalidate the whole cache.
- Clone detection: partition candidates by (LeftDoc,RightDoc) so the overlap
  merge is no longer O(C^2), and normalize document paths once for sorting.
- Memoize dynamically compiled TypeScript security regexes in a sync.Map.

Fix: clone detection built its document list via a closure side-effect and
returned nil, so ScanTargetFiles' cache hit skipped it and dropped every
quality.duplicate-code finding on any warm-cache scan. Route it through
VisitTargetFiles (bypasses the findings cache) instead.

Verified: go build/vet/gofmt clean, golangci-lint 0 issues, full suite passes
with -race, cold/warm scans byte-identical, ~1.2x-1.7x faster (small-large repos).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alxxjohn alxxjohn merged commit c79d9ec into main Jul 2, 2026
13 checks passed
@alxxjohn alxxjohn deleted the go-cleanup branch July 2, 2026 14:51
alxxjohn added a commit that referenced this pull request Jul 2, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.7.0](v0.6.1...v0.7.0)
(2026-07-02)


### Features

* **cli:** add What's New banner and task-grouped menu
([c1df999](c1df999))
* **cli:** What's New banner and task-grouped menu
([#21](#21))
([45e56f7](45e56f7))
* execute checks-improvement plan tiers 1-6
([733862f](733862f))
* **parsers:** tree-sitter TypeScript engine behind parsers.treesitter
flag
([5d09161](5d09161))


### Bug Fixes

* **ci:** extract version from var Number in homebrew validation
([01d7a24](01d7a24))
* **ci:** extract version from var Number in homebrew validation
([#23](#23))
([ec21f3b](ec21f3b))
* **version:** make Number a var so ldflags injection works
([6b1ccad](6b1ccad))


### Performance Improvements

* **scan:** parse/read files once, parallelize sections, scope cache
([d181a4f](d181a4f))
* **scan:** parse/read files once, parallelize sections, scope cache
([#24](#24))
([c79d9ec](c79d9ec))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

1 participant