Skip to content

Milestone 12 (part): benchmarks vs cgo pg_query_go + allocation profiling - #9

Merged
kyleconroy merged 2 commits into
mainfrom
claude/parser-profiling-benchmarks-o9ambe
Aug 17, 2026
Merged

Milestone 12 (part): benchmarks vs cgo pg_query_go + allocation profiling#9
kyleconroy merged 2 commits into
mainfrom
claude/parser-profiling-benchmarks-o9ambe

Conversation

@kyleconroy

Copy link
Copy Markdown
Contributor

Lands the benchmarking + memory-profiling portion of milestone 12: a benchmark suite comparing the pure-Go parser against the cgo pg_query_go, and fixes for the two allocation hotspots the profiles surfaced.

Benchmarks

  • benchmark_test.go (root) mirrors pg_query_go v6.2.2's benchmark file name-for-name — same queries, same globals trick — plus Scan/ParseToJSON variants and *Stress benchmarks over the corpus's largest input, the 1.1 MB multi-VALUES INSERT (fingerprint suite, case 073).
  • oracle/benchmark_test.go is the cgo twin: identical benchmark names and inputs against the pinned pg_query_go v6.2.2, so the two runs diff directly with benchstat (after normalizing the pkg: line).

Measured on 4 vCPU / go1.24.7, after the fixes below:

Area pure Go vs cgo
Raw parse, single-threaded 3–10% faster; 29% faster on the 1.1 MB stress query
Scan parity
Parse parallel (small queries) 6–26% slower (GC pays for every AST node; C-side arena allocations are invisible to Go accounting)
Normalize 1.6–3.6× slower
Fingerprint 4.3–6.3× slower
ParseToJSON ~5× slower

The normalize/fingerprint/JSON gaps share one root cause: those walks are protobuf-reflection-driven. PLAN.md already carried "generated per-node emitters remain an option for milestone 12 if profiling wants them" — the numbers say that's where the remaining time is, if those entry points ever matter to a consumer. (cgo's ~1 alloc/op in benchmark output is only the Go-side result copy, so allocs/op comparisons are only meaningful within the pure-Go column.)

Allocation fixes from profiling

  • Pre-size the parser's token buffer to len(input)/3 — append regrowth of parser.toks was 72% of parse alloc_space on the stress query. Stress parse: 92 MB → 39 MB and 185 ms → 146 ms per op; small queries lose a few allocs each.
  • Memoize the fingerprint walk's per-message-type field order instead of re-sorting field descriptors at every node visit: fingerprint runs 28–31% faster with ~25% fewer allocations.

What remains is the AST itself (~2 allocs per node: struct + ast.Node oneof wrapper) plus the protobuf marshal — 20–138 allocs on the upstream benchmark queries — which is the cost of the pg_query_go-compatible protobuf AST, not overhead to engineer away.

Also

  • Removed the accidentally committed 13 MB oracle/oracle binary and added a .gitignore for it.
  • PLAN.md gains "As-built notes (milestone 12, benchmarks + profiling)"; still open from the milestone: difftest mutation fuzzing in CI, go test -race over the corpus run, and the wasilibs comparison.

go test ./... is green — all 308,561 corpus cases pass byte-identically.

🤖 Generated with Claude Code

https://claude.ai/code/session_017iCgDQzsNy8EHzKGhY7P8S


Generated by Claude Code

claude added 2 commits August 17, 2026 05:24
…ling

Add benchmark_test.go mirroring pg_query_go v6.2.2's benchmark file
name-for-name, plus JSON/Scan variants and *Stress benchmarks over the
corpus's 1.1 MB multi-VALUES INSERT; oracle/benchmark_test.go is the cgo
twin with identical names and inputs so benchstat diffs the two runs
directly.

Fix the two allocation hotspots the stress profile surfaced:

- Pre-size the parser's token buffer to len(input)/3 — append regrowth
  was 72% of parse alloc_space. Stress parse: 92 MB -> 39 MB and
  185 ms -> 146 ms per op; small queries lose a few allocs each.
- Memoize the fingerprint walk's per-message-type field order instead of
  re-sorting descriptors at every node visit: fingerprint is 28-31%
  faster with ~25% fewer allocations.

Measured after the fixes (4 vCPU, go1.24.7): raw parse runs 3-10% faster
than cgo single-threaded and 29% faster on the stress query; Scan at
parity; the reflection-driven walks (fingerprint, normalize, ParseToJSON)
still trail cgo — recorded in PLAN.md's new as-built notes along with
what remains for the milestone (fuzzing, -race, wasilibs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iCgDQzsNy8EHzKGhY7P8S
@kyleconroy
kyleconroy merged commit 66ebdae into main Aug 17, 2026
2 checks passed
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