Skip to content

perf(generator): generating a large client is 3.5x faster and the generator builds with 45% less CPU - #139

Merged
Michael McQuade (giraffesyo) merged 1 commit into
canaryfrom
perf/faster-generation
Oct 21, 2026
Merged

Michael McQuade (giraffesyo) merged 1 commit into
canaryfrom
perf/faster-generation

Conversation

@giraffesyo

Copy link
Copy Markdown
Member

Core's SDK freshness job spent 26s of its Go SDK step compiling the generator and about 1.6s generating. This cuts both. Output on core's spec is byte-identical to canary.

Measured on core's 3.3 MB spec (1094 types):

Before After
CLI wall time 520ms ~150ms
Cold go build -a 24.0s wall, 75s CPU 9.2s wall, 41s CPU
Peak RSS 350 MB 280 MB

Changes

  • No goimports. It rebuilt its environment for every file (about 11ms each, even for an 84-byte file) and pulled in all of x/tools. WriteFiles now fixes imports against a fixed stdlib table (fixImports) and gofmts. Unit tests cover adding, pruning, collapsing to a single import, sibling-file names, shadowing locals, and struct-literal keys.
  • Parallel printing. Files over 64 KB print in chunks split at blank lines between top-level declarations: gofmt always prints that gap as one blank line, and no alignment or comment crosses it. Each chunk gets a private FileSet copy because token.File locks on every position lookup, which serialized the chunks. operations.go prints in 6ms instead of 48ms.
  • Concurrent template rendering.
  • No root libopenapi import. The parser calls datamodel, low/v3 and high/v3 directly, as BuildV3Model does, so the diffing, Arazzo and Swagger 2 packages no longer compile.
  • JSON fast path. libyaml tokenizing JSON as YAML flow syntax was most of the parse. parseJSONNode builds the same node tree (kinds, tags, styles, values, lines, columns), verified node by node against libyaml on every testdata spec and on core's spec. It defers to libyaml for \/, surrogate escapes, a BOM, or invalid JSON. Parse drops from 105ms to 48ms.
  • SkipMetadataCollection. The analyzer never reads the index's diagnostic metadata.

Tried and dropped: building component schemas in parallel before the analyzer, and raising GOGC. Both were slower end to end.

Testing

  • go test -race ./... passes, including the generate-and-compile e2e tests.
  • Generated core's SDK with canary and with this branch from the same spec copy: diff -r is empty.

…erator builds with 45% less CPU

On a 3.3 MB spec (1094 types), generation drops from about 520ms to 150ms,
and a cold build of the generator from 75s to 41s of CPU, which is most of
what `go tool` spends in a CI job without a build cache. Output is
byte-identical.

- WriteFiles no longer runs goimports, which rebuilt its environment for
  every file. It fixes imports against a fixed stdlib table and gofmts,
  printing large files in parallel chunks split at blank lines between
  top-level declarations. x/tools and x/mod leave go.mod.
- Templates render concurrently.
- The parser calls libopenapi's datamodel packages directly. The root
  package also compiled its diffing, Arazzo and Swagger 2 models.
- JSON specs skip libyaml: a JSON scanner builds the identical node tree,
  deferring to libyaml for anything it cannot reproduce exactly.
- The index skips diagnostic metadata the generator never reads.
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