Skip to content

perf: stop walking the package graph once per route - #31

Closed
nccapo wants to merge 1 commit into
perf/leaner-package-loadingfrom
perf/no-graph-walk-per-route
Closed

perf: stop walking the package graph once per route#31
nccapo wants to merge 1 commit into
perf/leaner-package-loadingfrom
perf/no-graph-walk-per-route

Conversation

@nccapo

@nccapo nccapo commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Stacked on #30. Review only this PR's diff; merge after #30.

Finding

This came out of profiling after #30, while checking whether a Go 1.27 toolchain bump would help. It won't help much — but the allocation profile pointed straight at us:

allocation source share of all objects
packages.VisitkeySlice 49.5%
↳ via resolver.findFuncDecl 26.8%
↳ via pipeline.findInfoForFuncDecl 22.8%

packages.Visit allocates and sorts a key slice for every package it walks, on every call. Five places called it once per route to answer a single question: which analyzed package was type-checked into this types.Package?

Change

The walk could never succeed for a dependency in the first place. Resolving a declaration needs syntax, and after #30 only the analyzed packages are parsed — dependencies are type-checked from export data and have none. So the scan goes over the analyzed packages directly.

Five call sites, in resolver (2), auth (2) and contract (2, one of which shared a helper), now go through a small packageWithTypes in each package. That set is also exactly what the credential scan and helper tracing are already confined to, so this narrows nothing that was reachable before.

Results

analyze, best of 5, /usr/bin/time -l for RSS:

corpus master #30 #30 + this total speedup
upstream RealWorld (27 routes) 0.695s 0.328s 0.259s 2.69x
synthetic (192 routes) 0.628s 0.193s 0.186s 3.38x
synthetic (800 routes) 0.861s 0.372s 0.290s 2.97x

Isolating the analysis phase on the 800-route service (total minus load):

#30 this PR
analysis time 265ms 116ms (2.3x)
allocated bytes / run 103 MB 66 MB
objects / run 645k 487k

Peak RSS on upstream: 633 MB on master → 45 MB.

Correctness

analyze --json output is identical to #30 across all 23 testdata corpora, the upstream RealWorld repository, and three synthetic services (24 / 192 / 800 routes). go build, go vet, go test ./..., golangci-lint and the pinned upstream corpus gate all pass.

Still open

pipeline.findInfoForFuncDecl and resolver.findFuncDecl still scan every declaration of every analyzed package per route — now cheaply, since the allocation is gone, but still O(routes × declarations). A map[token.Pos] built once per run would make both O(1). That is a shared-index refactor across four packages and belongs on its own; the allocation fix here was the part that did not need it.

🤖 Generated with Claude Code

packages.Visit allocates and sorts a key slice for every package it
walks, on every call. Five places called it once per route to answer one
question — which analyzed package was type-checked into this
types.Package — and together they were the largest source of allocation
in the analyzer: keySlice alone was 46% of every object allocated, and
Visit as a whole 49%.

The walk could not succeed for a dependency anyway. Resolving a
declaration needs syntax, and only the analyzed packages are parsed;
dependencies are type-checked from export data and have none. So the
scan is over the analyzed packages directly, which is also the set the
credential scan and helper tracing are already confined to.

On an 800-route service: analysis drops from 265ms to 116ms, allocated
bytes per run from 103MB to 66MB, and objects from 645k to 487k. End to
end with the previous change, 2.7x to 3.4x faster than master.

Output is identical across all 23 corpora, the upstream RealWorld
repository and three synthetic services.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nccapo nccapo closed this Sep 3, 2026
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