Skip to content

perf(parser,gosqlx): reserve the parse stack once before tokenizing [W1-04] - #940

Open
ajitpratap0 wants to merge 5 commits into
mainfrom
perf/w1-04-stack-pregrow
Open

ajitpratap0 wants to merge 5 commits into
mainfrom
perf/w1-04-stack-pregrow

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

A parse on a brand-new goroutine grew its stack 2 -> 4 -> 8 (-> 16) KiB inside the parse; for a ~1 us statement the 2-3 copies cost more than the parse. internal/stackgrow.Reserve() (12 KiB frame, lands a fresh goroutine at 16 KiB, no-op ~1 ns afterwards) now runs first in tokscratch.Bytes/String/StringContext (covers every internal tokenize-then-parse pipeline), in four public-tokenizer pipelines (ParseBytesWithTokens, Analyze, LintString, LSP parseWithRecoveryForDialect) and as a backstop in the parser core (parseTokens, parseContextTokens, parseWithRecovery). No output, allocs/op or B/op change; no public API (D18).

Memory: a goroutine that parses keeps >= 16 KiB stack until exit/GC shrink (before 4-16 KiB; Windows lands at 32 KiB). Coverage over the 2,999-statement corpus: 99.90% need no further copy.

Also: BenchmarkParseFreshRW (evidence for the 16 KiB class on real-world SQL) and the CI non-race step now runs AllocBudget|StackBudget|StackReserve for ./pkg/gosqlx ./internal/stackgrow.

Test Plan

  • go build ./..., GOOS=js GOARCH=wasm go build ./wasm/: pass

  • go test -count=1 -run 'StackReserve|StackBudget|EntryPoints|NoUnreserved|Reserve' ./internal/stackgrow ./pkg/gosqlx (non-race and -race): pass; real-world coverage 2996/2999

  • go test -count=1 -run 'AllocBudget|StackBudget|StackReserve' ./pkg/gosqlx ./internal/stackgrow: pass

  • task test:cbinding, go test ./pkg/compatibility/..., staticcheck on changed packages: pass

  • task check: format/vet/lint clean; the race run hit the 2m timeout and timing-ratio flakes (TestJoinWithComments_ScalesLinearly, TestTranspileDeepChainLinear, parser/security/tokenizer timeouts) while the M4 was at load 50-114. Re-run: parser, security, tokenizer, transpiler pass; pkg/formatter passes alone and flaked once more under the full-tree run (B/op ratio 4.31 within bound; only the wall-clock ratio missed). No test, threshold or timeout was changed.

  • Mutation checks (by Codex): reserveBytes=16<<10 fails T2, 5<<10 fails T4, never as const fails T1, dropping the Reserve in tokscratch.String fails T5.

  • Local task bench:ab BASE=origin/main BENCH='./pkg/gosqlx=^BenchmarkParseFresh$' ROUNDS=8 LAYOUTS=0 BENCHTIME=200ms (arm64 M4, load 50+, indicative only): geomean -64.88%; simple_select -79.8%, aggregate -74.1%, subquery -68.0%; other cells directionally lower but noisy (+-80%). allocs/op and B/op identical in every cell.

  • CI perf-gate (ci(perf): randomize code layout in the A/B perf gate so layout noise cannot read as a regression #934, amd64 EPYC 7763, 20 interleaved rounds x 20 randomized layouts per arm, per-layout Mann-Whitney U, A/A', BH). Authoritative; local M4 numbers are indicative only. The gate suite now includes ParseFresh insert/update/delete (scripts/perf/gate-suite.txt), so the short-DML targets are gated.

    Run 1 (base 6399dc0, head merge 58e1cfa): PASS. simple_select -58.73%, join -43.63%, complex -23.54% (all p<0.001, A/A' about -0.2%); instructions/op -60.5%, -42.0%, -24.2%; no gated time regression; allocs/op and B/op unchanged; suite geomean -2.96%; A/A' moved 0 of 52 benchmarks; no target in the Low-power list.

    Run 2 (base a72ec74 = current main, head 21e8482, includes insert/update/delete):

    benchmark time p A/A' B/op allocs/op
    ParseFresh/simple_select -58.54% <0.001 +0.33% +0.00% 6 -> 6
    ParseFresh/join -43.35% <0.001 +0.58% +0.00% 18 -> 18
    ParseFresh/complex -23.29% <0.001 -0.11% +0.00% 50 -> 50
    ParseFresh/insert -56.94% <0.001 +0.01% +0.00% 18 -> 18
    ParseFresh/update -59.37% <0.001 -0.07% +0.00% 14 -> 14
    ParseFresh/delete -66.13% <0.001 +0.44% +0.00% 6 -> 6

    Suite geomean -7.69% (p<0.001); A/A' 0 of 55 moved, geomean -0.02%.

    Gate noise, reported not hidden: the shadow verdict reads "regression" in both PR-head runs for reasons unrelated to this change. Run (base a72ec74, head 4e90e34) flagged ParseRW/clickhouse/medium +3.22% (A/A' +2.18%, Low-power list, instructions/op -0.91%); it did not reproduce in run 2 (-0.30%, p=0.735) nor in run 1 (PASS). Run 2 flagged LintRW/postgresql allocs/op 34189 -> 34190 (+1 in 34k; run 1 measured 34189.5 -> 34189, so this is pool/GC amortization jitter in a benchmark whose Lint path this change does not touch).

  • PLAN W1-04 threshold MISSED on one cell: it requires BenchmarkParseFresh/* <= -40%; ParseFresh/complex reaches only -23.3% to -23.5% on amd64 (the remaining cost is parse work, not stack growth). simple_select, join (-43%), insert, update, delete all meet it. The W3-06 trigger ("complex above 1.0x Vitess") still has to be re-checked against this result.

Open decisions (recommended defaults implemented): M1 16 KiB landing; M2(a) implemented: scripts/perf/gate-suite.txt is widened, so the short-DML cells (insert/update/delete) are in the gated suite.

Breaking Changes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

…W1-04]

A parse on a fresh goroutine grew its stack 2 -> 4 -> 8 (-> 16) KiB inside
the parse. internal/stackgrow.Reserve (12 KiB frame, lands at 16 KiB) now
runs first in tokscratch, four public-tokenizer pipelines and the parser
core, so the stack is copied once while shallow. No output, allocs/op or
B/op change; no public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
gosqlx-website Ready Ready Preview Nov 3, 2026 7:53pm UTC

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 24s —— View job


Review of PR #940: reserve the parse stack once before tokenizing [W1-04]

I read the diff of the single commit. I did not build or run the tests in this job. The test results below come from the PR description.

Verdict: the approach is sound and the change is small and well contained. I have no blocking issues, only a few non-blocking points.

What I checked

  • internal/stackgrow.Reserve (stackgrow.go)
    • The 12 KiB frame is above the compiler's "big frame" threshold, so the prologue forces a morestack call. //go:noinline on both Reserve and sink keeps the frame from being inlined away.
    • Taking &buf and passing it to a noinline callee doesn't make the array escape, so it stays on the stack. The array is never zeroed because never is never true. The mutation checks in the test plan (never as a const fails T1) confirm this is guarded.
    • Because the package is internal, there is no API surface or compatibility concern.
  • Call sites
    • tokscratch.Bytes/String/StringContext cover the internal pipelines.
    • The four public pipelines and the three parser-core backstops add one call each.
    • When the stack is already large enough, Reserve is a call and a compare, about 1 ns.
  • Pooling and concurrency: nothing here touches the pooled tokenizer, parser or AST objects, so the pooling and race-freedom rules in CLAUDE.md are unaffected.
  • CI: the non-race step now runs AllocBudget|StackBudget|StackReserve. That fits the repo's rule that stack and alloc budgets skip under -race.

Non-blocking suggestions

  1. Fragility across toolchains. The benefit depends on the Go runtime's stack-growth policy (doubling from 2 KiB, with the StackBig/StackSmall thresholds) and on the compiler not optimizing the frame away. The tests catch regressions, but they assert a landing size. A Go upgrade could change the landing class (the package doc already notes Windows 32 KiB and race 16-32 KiB). Make sure the stack-reserve tests fail with a clear message that points at the doc comment when that happens, not only on a bare size mismatch.
  2. Memory trade-off. Every goroutine that parses now keeps at least 16 KiB until it exits or a GC shrink. This is documented in the CHANGELOG. For servers that spawn a goroutine per request and parse once, it adds roughly 4-14 KiB per goroutine. For a library that is often used that way, a short "Memory" note in docs/PERFORMANCE_METHODOLOGY.md or the user docs would be worth adding. Worker pools see no change.
  3. Redundant Reserve calls. LintString, Analyze and LSP parseWithRecoveryForDialect call Reserve, and then reach the tokscratch or parser-core call again. The repeat cost is about 1 ns and the layering is intentional, so this is fine. A one-line comment at those sites ("entry reserve; core also backstops") would help the next reader. Only the parser sites carry one today.
  4. CHANGELOG.md placement. The entry is under ### Performance, which is correct. The text is long. The "amd64 gate numbers are in the PR" placeholder needs real numbers or the sentence removed before release, since the CHANGELOG outlives the PR.
  5. Evidence.
    • The local A/B result (-64.88% geomean) was measured at load 50-114 on the M4 and is flagged as indicative. The amd64 perf-gate verdict is the real evidence per the repo's "measure, don't assume" principle, so please wait for it before merging.
    • Short DML (insert/update/delete) is where the gain is largest, and those cells are not in gate-suite.txt (decision M2). I'd lean toward widening the suite so a future regression here is gated.
  6. Test flakes noted in the PR body. The timing-ratio flakes (TestJoinWithComments_ScalesLinearly, TestTranspileDeepChainLinear, formatter wall-clock ratio) were seen under heavy machine load and are unrelated to this change. A clean CI run of task test:race is still worth confirming before merge.

Security

No new input handling, no unsafe, and no new allocations of attacker-controlled size. The reserved size is a compile-time constant. No concerns.

Test coverage

Coverage looks thorough. It includes the entry-point enumeration test (NoUnreserved), the corpus coverage check (2996/2999), the budget tests and the mutation checks.

Overall this looks good to merge once the amd64 perf gate confirms the gain.

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci-cd lsp parser linter labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Perf gate: PASS (shadow mode: report only, this check never fails)

Base 6d29d6802dc6, head cedf5d210a5b. 20 interleaved rounds, 20 randomized code layouts per arm (-randlayout seeds 417072189982..417072190001; A/A' 417072190002..417072190021), benchtime 100ms, -cpu 1, GOMEMLIMIT 64MiB, pinned to CPU 3 (SMT siblings 2-3) with taskset.

Runner CPU: INTEL(R) XEON(R) PLATINUM 8573C (x86_64). Base and head ran interleaved in this job, so the tables show their ratios only; absolute timings (raw files in the artifact) hold for this CPU and this job alone and are never compared across jobs.

  • 6 benchmark(s) faster by 3% or more

Rules: each arm runs 20 randomized code layouts (-ldflags=-randlayout); time regresses at >= +3% when significant at a false discovery rate of 0.01 over the suite (Benjamini-Hochberg on exact Mann-Whitney U p-values of the per-layout medians, so a regression must hold across layouts, not within one binary), or a suite geomean >= +1.5% (per-layout suite geomeans, same test); allocs/op must not increase; B/op must not rise more than 2%; instructions/op must not rise more than 1.5%. The A/A' control runs the base revision under a disjoint set of layout seeds; when more than 10% of benchmarks move over 3% in it, the run is noisy: re-run once, then inconclusive (neutral).

Gated suite (GOGC=off, GOMAXPROCS=1)

benchmark time p A/A' spread B/op allocs/op status
ParseE2E/simple_select +0.16% 0.665 -0.81% 1.13% +0.00% 5 → 5 ok
ParseE2E/select_where +0.52% 0.310 -0.82% 1.35% +0.00% 18 → 18 ok
ParseE2E/join -0.23% 0.457 -0.75% 0.82% +0.00% 17 → 17 ok
ParseE2E/aggregate +0.12% 0.756 +0.00% 1.02% +0.00% 18 → 18 ok
ParseE2E/subquery +0.23% 0.552 -0.36% 1.15% +0.00% 14 → 14 ok
ParseE2E/cte +0.88% 0.262 +0.21% 2.96% +0.00% 21 → 21 ok
ParseE2E/window +0.55% 0.330 -0.51% 2.73% +0.00% 11 → 11 ok
ParseE2E/complex +0.18% 0.718 -1.14% 1.06% +0.00% 49 → 49 ok
ParseE2E/insert +0.65% 0.267 +0.27% 1.22% +0.00% 17 → 17 ok
ParseE2E/update -0.52% 0.441 -0.76% 2.39% +0.00% 13 → 13 ok
ParseE2E/delete +0.19% 0.705 -0.95% 1.25% +0.00% 5 → 5 ok
ParseRW/postgresql/medium -0.02% 0.989 -0.00% 1.15% +0.00% 646 → 646 ok
ParseRW/mysql/medium +0.23% 0.529 +0.05% 0.86% +0.00% 368 → 368 ok
ParseRW/mariadb/medium +0.09% 0.659 +0.32% 0.91% -0.00% 528 → 528 ok
ParseRW/sqlserver/medium +0.02% 0.978 -0.01% 1.40% -0.00% 927 → 927 ok
ParseRW/oracle/medium -0.48% 0.121 -0.18% 1.08% -0.00% 1090 → 1090 ok
ParseRW/sqlite/medium -0.38% 0.547 +0.04% 1.41% +0.00% 347 → 347 ok
ParseRW/snowflake/medium -0.35% 0.583 -0.03% 1.53% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium -0.15% 0.779 +0.01% 1.48% +0.00% 383 → 383 ok
ParseFresh/simple_select -60.16% <0.001 -0.11% 2.03% +0.00% 6 → 6 faster
ParseFresh/join -39.74% <0.001 +0.44% 0.92% +0.00% 18 → 18 faster
ParseFresh/complex -22.72% <0.001 +0.06% 1.29% +0.00% 50 → 50 faster
ParseFresh/insert -57.20% <0.001 +0.16% 1.67% +0.00% 18 → 18 faster
ParseFresh/update -59.95% <0.001 +0.02% 1.29% +0.00% 14 → 14 faster
ParseFresh/delete -67.09% <0.001 +0.32% 1.26% +0.00% 6 → 6 faster
PoolGetPut/serial +0.09% 0.256 -0.04% 0.30% +0.00% 0 → 0 ok
Tokenize/scratch/join -1.16% 0.113 +0.41% 2.44% +0.00% 0 → 0 ok
Tokenize/scratch/complex -1.45% 0.088 +0.49% 2.49% +0.00% 0 → 0 ok
Tokenize/scratch/insert -1.77% 0.063 -0.52% 3.16% +0.00% 0 → 0 ok
Normalize/simple_select -0.20% 0.597 -0.41% 0.72% +0.00% 7 → 7 ok
Normalize/join +0.58% 0.040 +0.00% 0.81% +0.00% 23 → 23 ok
Normalize/complex +0.27% 0.262 +0.01% 0.63% +0.00% 68 → 68 ok
TransformParse/simple_select +0.71% 0.048 +0.58% 0.80% +0.00% 5 → 5 ok
TransformParse/join -0.26% 0.598 -0.66% 1.64% +0.00% 17 → 17 ok
TransformParse/complex -0.99% 0.301 +0.81% 2.28% +0.00% 49 → 49 ok
LintParallel/rules +0.41% 0.495 +0.47% 1.95% +0.00% 158312.5 → 158312 ok
LintParallel/lint -0.23% 0.620 -0.53% 0.86% +0.00% 236886.5 → 236887 ok
LintRW/postgresql -0.77% 0.301 +0.74% 1.66% +0.00% 26500 → 26500 ok
LintRW/mysql +0.30% 0.495 -0.70% 2.00% -0.00% 32413 → 32413 ok
LintRW/mariadb +0.45% 0.461 +0.29% 2.65% +0.00% 29610 → 29610 ok
LintRW/sqlserver -0.09% 0.820 -0.23% 1.05% +0.00% 25622 → 25622 ok
LintRW/oracle -0.67% 0.192 -0.15% 1.24% +0.00% 31614 → 31614 ok
LintRW/sqlite -0.50% 0.398 -0.13% 1.93% -0.00% 29427 → 29427 ok
LintRW/snowflake -1.10% 0.033 -0.18% 1.04% -0.00% 28805 → 28805 ok
LintRW/clickhouse +0.28% 0.495 +0.10% 1.01% -0.00% 32891 → 32891 ok
LintRule/L007 +0.41% 0.398 -0.03% 1.27% +0.00% 1194 → 1194 ok
LintRule/L010 +0.16% 0.398 +0.05% 0.61% +0.00% 0 → 0 ok
ParseError/syntax_mid/parse -0.74% 0.213 -0.80% 1.77% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate -0.11% 0.850 -0.33% 1.99% +0.00% 12 → 12 ok
ParseError/syntax_end/parse +0.07% 0.903 -0.15% 1.74% +0.00% 16 → 16 ok
ParseError/syntax_end/validate +0.22% 0.745 -0.36% 2.01% +0.00% 16 → 16 ok
ParseError/tokenize/parse +0.46% 0.358 -0.07% 1.31% +0.00% 4 → 4 ok
ParseError/tokenize/validate -0.04% 0.903 -0.39% 2.03% +0.00% 21 → 21 ok
ParseError/long_prefix/parse +0.50% 0.310 -0.42% 1.48% +0.00% 219 → 219 ok
ParseError/long_prefix/validate +1.36% 0.006 +0.00% 1.68% +0.00% 219 → 219 ok
geomean -7.99% <0.001 -0.12%

time and A/A' are Hodges-Lehmann shifts over the per-layout medians. spread: the base arm's robust standard deviation across its layouts (1.4826 x MAD, relative), i.e. how much this benchmark moves between builds that differ only in function placement, plus run-to-run noise; a change much smaller than it cannot be resolved in one run.

A/A' control (base revision, disjoint layout seeds): 0 of 55 benchmarks moved more than 3%; 0 would have been flagged by the time rule; suite geomean -0.12%, p 0.383.

Low power

Every benchmark has an estimated 80% or better chance of being flagged on its own for a +5% slowdown.

Instructions per operation (cmd/perfdriver)

case instructions/op status
e2e/aggregate +0.11% ok
e2e/complex +0.01% ok
e2e/cte +0.13% ok
e2e/delete +0.25% ok
e2e/insert +0.25% ok
e2e/join +0.07% ok
e2e/select_where +0.14% ok
e2e/simple_select +0.28% ok
e2e/subquery +0.14% ok
e2e/update +0.25% ok
e2e/window +0.16% ok
rw/postgresql/medium +0.08% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select +0.20% 0.734
ParseE2E/select_where +1.21% 0.009
ParseE2E/join -0.42% 0.473
ParseE2E/aggregate +0.61% 0.212
ParseE2E/subquery +0.35% 0.705
ParseE2E/cte +0.75% 0.406
ParseE2E/window +0.06% 0.791
ParseE2E/complex +0.47% 0.631
ParseE2E/insert -0.14% 0.677
ParseE2E/update +0.25% 0.684
ParseE2E/delete -0.03% 1.000
ParseRW/postgresql/medium -0.87% 0.105
ParseRW/mysql/medium -0.09% 0.739
ParseRW/mariadb/medium -0.31% 0.393
ParseRW/sqlserver/medium -1.02% 0.019
ParseRW/oracle/medium -0.02% 0.971
ParseRW/sqlite/medium -0.30% 0.529
ParseRW/snowflake/medium -0.34% 0.436
ParseRW/clickhouse/medium -0.59% 0.075
ParseFresh/simple_select -61.35% <0.001
ParseFresh/join -40.86% <0.001
ParseFresh/complex -24.00% <0.001
ParseFresh/insert -57.52% <0.001
ParseFresh/update -60.31% <0.001
ParseFresh/delete -67.57% <0.001
PoolGetPut/serial +0.10% 0.472
Tokenize/scratch/join -1.29% 0.325
Tokenize/scratch/complex -1.45% 0.436
Tokenize/scratch/insert -2.28% 0.190
Normalize/simple_select -0.05% 0.850
Normalize/join +0.29% 0.481
Normalize/complex +0.45% 0.684
TransformParse/simple_select +1.14% 0.043
TransformParse/join +0.59% 0.529
TransformParse/complex +0.24% 0.853
LintParallel/rules -0.34% 0.684
LintParallel/lint +0.26% 0.529
LintRW/postgresql +0.16% 0.631
LintRW/mysql +0.42% 0.063
LintRW/mariadb +0.23% 0.436
LintRW/sqlserver +0.47% 0.075
LintRW/oracle +0.37% 0.218
LintRW/sqlite +0.06% 0.971
LintRW/snowflake +0.21% 0.353
LintRW/clickhouse -0.15% 0.853
LintRule/L007 +1.03% 0.218
LintRule/L010 +0.64% 0.105
ParseError/syntax_mid/parse +0.07% 0.853
ParseError/syntax_mid/validate -0.08% 1.000
ParseError/syntax_end/parse +0.04% 1.000
ParseError/syntax_end/validate +1.00% 0.226
ParseError/tokenize/parse +1.28% 0.121
ParseError/tokenize/validate +0.14% 0.796
ParseError/long_prefix/parse -0.69% 0.165
ParseError/long_prefix/validate +0.52% 0.529
geomean -8.07%
Environment
date:        2026-11-03T20:10:33Z
base:        6d29d6802dc6c910264e0d0ee4ff820a323fd2a9
head:        working tree of cedf5d210a5b5e1536871ca93d42ae5778edc619
cpu model:   INTEL(R) XEON(R) PLATINUM 8573C (x86_64)
go:          go version go1.26.9 linux/amd64
kernel:      Linux 7.0.0-1017-azure x86_64
layouts:     20 per arm, -ldflags=-randlayout seeds 417072189982..417072190001 (base, head), 417072190002..417072190021 (A/A')
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        7.58, 2.60, 0.93
instr count: cachegrind
perf probe:  Error: No supported events found. The instructions:u event is not supported. 

Architecture:                            x86_64
CPU(s):                                  4
Model name:                              INTEL(R) XEON(R) PLATINUM 8573C
Thread(s) per core:                      2
Core(s) per socket:                      2
Socket(s):                               1
CPU(s) scaling MHz:                      135%
Hypervisor vendor:                       Microsoft
L1d cache:                               96 KiB (2 instances)
L1i cache:                               64 KiB (2 instances)
L2 cache:                                4 MiB (2 instances)
L3 cache:                                260 MiB (1 instance)
benchstat (primary metric, samples pooled across layouts: informational, the verdict uses the per-layout test)
goos: linux
goarch: amd64
pkg: github.com/ajitpratap0/GoSQLX/pkg/fingerprint
cpu: INTEL(R) XEON(R) PLATINUM 8573C
                        │    base     │                head                │
                        │   sec/op    │   sec/op     vs base               │
Normalize/simple_select   1.969µ ± 0%   1.964µ ± 1%       ~ (p=0.597 n=20)
Normalize/join            6.130µ ± 1%   6.168µ ± 0%       ~ (p=0.038 n=20)
Normalize/complex         17.20µ ± 1%   17.26µ ± 1%       ~ (p=0.262 n=20)
geomean                   5.920µ        5.935µ       +0.25%

                        │     base     │                 head                  │
                        │     B/op     │     B/op      vs base                 │
Normalize/simple_select     896.0 ± 0%     896.0 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/join            2.211Ki ± 0%   2.211Ki ± 0%       ~ (p=1.000 n=20) ¹
Normalize/complex         6.117Ki ± 0%   6.117Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                   2.279Ki        2.279Ki       +0.00%
¹ all samples are equal

                        │    base    │                head                 │
                        │ allocs/op  │ allocs/op   vs base                 │
Normalize/simple_select   7.000 ± 0%   7.000 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/join            23.00 ± 0%   23.00 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/complex         68.00 ± 0%   68.00 ± 0%       ~ (p=1.000 n=20) ¹
geomean                   22.20        22.20       +0.00%
¹ all samples are equal

pkg: github.com/ajitpratap0/GoSQLX/pkg/gosqlx
                                │    base     │                head                 │
                                │   sec/op    │   sec/op     vs base                │
ParseE2E/simple_select            1.247µ ± 1%   1.252µ ± 1%        ~ (p=0.663 n=20)
ParseE2E/select_where             3.193µ ± 1%   3.202µ ± 1%        ~ (p=0.311 n=20)
ParseE2E/join                     4.320µ ± 1%   4.304µ ± 1%        ~ (p=0.457 n=20)
ParseE2E/aggregate                3.333µ ± 1%   3.329µ ± 1%        ~ (p=0.753 n=20)
ParseE2E/subquery                 2.650µ ± 1%   2.657µ ± 1%        ~ (p=0.551 n=20)
ParseE2E/cte                      3.754µ ± 5%   3.923µ ± 5%        ~ (p=0.262 n=20)
ParseE2E/window                   2.468µ ± 2%   2.490µ ± 2%        ~ (p=0.331 n=20)
ParseE2E/complex                  11.94µ ± 1%   11.92µ ± 3%        ~ (p=0.718 n=20)
ParseE2E/insert                   2.009µ ± 1%   2.026µ ± 2%        ~ (p=0.268 n=20)
ParseE2E/update                   1.735µ ± 2%   1.724µ ± 1%        ~ (p=0.441 n=20)
ParseE2E/delete                   1.064µ ± 1%   1.065µ ± 1%        ~ (p=0.703 n=20)
ParseRW/postgresql/medium         134.2µ ± 1%   134.5µ ± 2%        ~ (p=0.989 n=20)
ParseRW/mysql/medium              77.76µ ± 1%   77.92µ ± 1%        ~ (p=0.529 n=20)
ParseRW/mariadb/medium            112.3µ ± 1%   112.5µ ± 1%        ~ (p=0.659 n=20)
ParseRW/sqlserver/medium          246.5µ ± 1%   246.2µ ± 0%        ~ (p=0.974 n=20)
ParseRW/oracle/medium             294.0µ ± 1%   292.6µ ± 1%        ~ (p=0.121 n=20)
ParseRW/sqlite/medium             61.97µ ± 1%   61.75µ ± 1%        ~ (p=0.547 n=20)
ParseRW/snowflake/medium          171.8µ ± 1%   171.8µ ± 1%        ~ (p=0.583 n=20)
ParseRW/clickhouse/medium         77.00µ ± 6%   76.88µ ± 5%        ~ (p=0.779 n=20)
ParseFresh/simple_select          6.235µ ± 2%   2.481µ ± 1%  -60.21% (p=0.000 n=20)
ParseFresh/join                   9.382µ ± 7%   5.681µ ± 1%  -39.45% (p=0.000 n=20)
ParseFresh/complex                17.52µ ± 1%   13.56µ ± 1%  -22.61% (p=0.000 n=20)
ParseFresh/insert                 7.872µ ± 1%   3.361µ ± 1%  -57.30% (p=0.000 n=20)
ParseFresh/update                 7.725µ ± 1%   3.088µ ± 1%  -60.03% (p=0.000 n=20)
ParseFresh/delete                 7.200µ ± 2%   2.376µ ± 1%  -67.01% (p=0.000 n=20)
PoolGetPut/serial                 93.97n ± 0%   94.05n ± 0%        ~ (p=0.256 n=20)
ParseError/syntax_mid/parse       2.014µ ± 1%   1.993µ ± 1%        ~ (p=0.213 n=20)
ParseError/syntax_mid/validate    1.826µ ± 1%   1.828µ ± 1%        ~ (p=0.846 n=20)
ParseError/syntax_end/parse       2.296µ ± 1%   2.292µ ± 1%        ~ (p=0.899 n=20)
ParseError/syntax_end/validate    2.245µ ± 1%   2.247µ ± 1%        ~ (p=0.743 n=20)
ParseError/tokenize/parse         846.7n ± 1%   849.5n ± 1%        ~ (p=0.358 n=20)
ParseError/tokenize/validate      2.334µ ± 1%   2.336µ ± 1%        ~ (p=0.899 n=20)
ParseError/long_prefix/parse      57.13µ ± 1%   57.54µ ± 1%        ~ (p=0.311 n=20)
ParseError/long_prefix/validate   54.60µ ± 1%   55.42µ ± 1%   +1.49% (p=0.005 n=20)
geomean                           8.345µ        7.314µ       -12.36%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            19.88Mi ± 1%   19.81Mi ± 1%       ~ (p=0.654 n=20)
ParseE2E/select_where             30.77Mi ± 1%   30.68Mi ± 1%       ~ (p=0.317 n=20)
ParseE2E/join                     27.82Mi ± 1%   27.92Mi ± 1%       ~ (p=0.449 n=20)
ParseE2E/aggregate                24.04Mi ± 1%   24.07Mi ± 0%       ~ (p=0.784 n=20)
ParseE2E/subquery                 29.52Mi ± 1%   29.43Mi ± 1%       ~ (p=0.551 n=20)
ParseE2E/cte                      31.50Mi ± 5%   30.15Mi ± 6%       ~ (p=0.274 n=20)
ParseE2E/window                   36.33Mi ± 2%   36.02Mi ± 2%       ~ (p=0.324 n=20)
ParseE2E/complex                  30.20Mi ± 1%   30.24Mi ± 3%       ~ (p=0.683 n=20)
ParseE2E/insert                   36.08Mi ± 1%   35.77Mi ± 2%       ~ (p=0.268 n=20)
ParseE2E/update                   30.24Mi ± 2%   30.43Mi ± 1%       ~ (p=0.441 n=20)
ParseE2E/delete                   46.61Mi ± 1%   46.57Mi ± 1%       ~ (p=0.713 n=20)
ParseRW/postgresql/medium         26.74Mi ± 1%   26.66Mi ± 2%       ~ (p=0.973 n=20)
ParseRW/mysql/medium              34.80Mi ± 1%   34.74Mi ± 1%       ~ (p=0.525 n=20)
ParseRW/mariadb/medium            31.58Mi ± 1%   31.51Mi ± 1%       ~ (p=0.683 n=20)
ParseRW/sqlserver/medium          26.99Mi ± 1%   27.03Mi ± 0%       ~ (p=0.941 n=20)
ParseRW/oracle/medium             28.51Mi ± 1%   28.64Mi ± 1%       ~ (p=0.119 n=20)
ParseRW/sqlite/medium             33.34Mi ± 1%   33.45Mi ± 1%       ~ (p=0.533 n=20)
ParseRW/snowflake/medium          29.60Mi ± 1%   29.60Mi ± 1%       ~ (p=0.583 n=20)
ParseRW/clickhouse/medium         29.27Mi ± 7%   29.32Mi ± 5%       ~ (p=0.763 n=20)
ParseError/syntax_mid/parse       22.73Mi ± 1%   22.97Mi ± 1%       ~ (p=0.199 n=20)
ParseError/syntax_mid/validate    25.06Mi ± 1%   25.04Mi ± 1%       ~ (p=0.857 n=20)
ParseError/syntax_end/parse       16.20Mi ± 1%   16.23Mi ± 1%       ~ (p=0.888 n=20)
ParseError/syntax_end/validate    16.57Mi ± 1%   16.56Mi ± 1%       ~ (p=0.743 n=20)
ParseError/tokenize/parse         51.81Mi ± 1%   51.64Mi ± 1%       ~ (p=0.365 n=20)
ParseError/tokenize/validate      18.80Mi ± 1%   18.78Mi ± 1%       ~ (p=0.931 n=20)
ParseError/long_prefix/parse      40.44Mi ± 1%   40.14Mi ± 1%       ~ (p=0.331 n=20)
ParseError/long_prefix/validate   42.30Mi ± 1%   41.68Mi ± 1%  -1.48% (p=0.005 n=20)
geomean                           29.16Mi        29.08Mi       -0.26%

                                │      base      │                 head                  │
                                │      B/op      │     B/op      vs base                 │
ParseE2E/simple_select              816.0 ± 0%       816.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/select_where             1.523Ki ± 0%     1.523Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/join                     1.836Ki ± 0%     1.836Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/aggregate                1.812Ki ± 0%     1.812Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/subquery                 1.828Ki ± 0%     1.828Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/cte                      2.336Ki ± 0%     2.336Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/window                   1.281Ki ± 0%     1.281Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/complex                  5.025Ki ± 0%     5.025Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/insert                     824.0 ± 0%       824.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/update                     736.0 ± 0%       736.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/delete                     400.0 ± 0%       400.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/postgresql/medium         65.90Ki ± 0%     65.90Ki ± 0%       ~ (p=0.677 n=20)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=0.748 n=20)
ParseRW/mariadb/medium            60.60Ki ± 0%     60.60Ki ± 0%       ~ (p=0.547 n=20)
ParseRW/sqlserver/medium          83.68Ki ± 0%     83.67Ki ± 0%       ~ (p=0.333 n=20)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=0.023 n=20)
ParseRW/sqlite/medium             38.12Ki ± 0%     38.12Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/snowflake/medium          77.63Ki ± 0%     77.63Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/clickhouse/medium         34.82Ki ± 0%     34.82Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/simple_select            896.0 ± 0%       896.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/join                   1.914Ki ± 0%     1.914Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/complex                5.102Ki ± 0%     5.102Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/insert                   904.0 ± 0%       904.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/update                   816.0 ± 0%       816.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/delete                   480.0 ± 0%       480.0 ± 0%       ~ (p=1.000 n=20) ¹
PoolGetPut/serial                   0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/parse         680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/validate      680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/parse       1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/validate    1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/parse           272.0 ± 0%       272.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/validate      1.758Ki ± 0%     1.758Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/parse      22.75Ki ± 0%     22.75Ki ± 0%       ~ (p=0.657 n=20)
ParseError/long_prefix/validate   22.68Ki ± 0%     22.68Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                                        ²                 -0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                │     base      │                 head                 │
                                │   allocs/op   │  allocs/op   vs base                 │
ParseE2E/simple_select             5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/select_where              18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/join                      17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/aggregate                 18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/subquery                  14.00 ± 0%      14.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/cte                       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/window                    11.00 ± 0%      11.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/complex                   49.00 ± 0%      49.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/insert                    17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/update                    13.00 ± 0%      13.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/delete                    5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/postgresql/medium          646.0 ± 0%      646.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/mysql/medium               368.0 ± 0%      368.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/mariadb/medium             528.0 ± 0%      528.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/sqlserver/medium           927.0 ± 0%      927.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/oracle/medium             1.090k ± 0%     1.090k ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/sqlite/medium              347.0 ± 0%      347.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/snowflake/medium           776.0 ± 0%      776.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/clickhouse/medium          383.0 ± 0%      383.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/simple_select           6.000 ± 0%      6.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/join                    18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/complex                 50.00 ± 0%      50.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/insert                  18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/update                  14.00 ± 0%      14.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/delete                  6.000 ± 0%      6.000 ± 0%       ~ (p=1.000 n=20) ¹
PoolGetPut/serial                  0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/parse        12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/validate     12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/parse        16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/validate     16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/parse          4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/validate       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/parse       219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/validate    219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=20) ¹
geomean                                       ²                +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                          │    base     │                head                │
                          │  sec/stmt   │  sec/stmt    vs base               │
ParseRW/postgresql/medium   11.18µ ± 1%   11.21µ ± 2%       ~ (p=0.984 n=20)
ParseRW/mysql/medium        9.720µ ± 1%   9.740µ ± 1%       ~ (p=0.524 n=20)
ParseRW/mariadb/medium      12.47µ ± 1%   12.50µ ± 1%       ~ (p=0.664 n=20)
ParseRW/sqlserver/medium    11.74µ ± 1%   11.72µ ± 0%       ~ (p=0.941 n=20)
ParseRW/oracle/medium       12.25µ ± 1%   12.19µ ± 1%       ~ (p=0.122 n=20)
ParseRW/sqlite/medium       10.33µ ± 1%   10.29µ ± 1%       ~ (p=0.533 n=20)
ParseRW/snowflake/medium    13.21µ ± 1%   13.21µ ± 1%       ~ (p=0.578 n=20)
ParseRW/clickhouse/medium   11.00µ ± 6%   10.98µ ± 5%       ~ (p=0.774 n=20)
geomean                     11.44µ        11.43µ       -0.05%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                   │    base     │                head                │
                   │   sec/op    │   sec/op     vs base               │
LintParallel/rules   26.52m ± 2%   26.78m ± 0%       ~ (p=0.495 n=20)
LintParallel/lint    44.53m ± 1%   44.48m ± 2%       ~ (p=0.620 n=20)
LintRW/postgresql    5.790m ± 1%   5.708m ± 2%       ~ (p=0.301 n=20)
LintRW/mysql         6.282m ± 1%   6.320m ± 1%       ~ (p=0.495 n=20)
LintRW/mariadb       5.766m ± 2%   5.775m ± 1%       ~ (p=0.461 n=20)
LintRW/sqlserver     5.456m ± 1%   5.444m ± 1%       ~ (p=0.820 n=20)
LintRW/oracle        6.486m ± 1%   6.437m ± 2%       ~ (p=0.192 n=20)
LintRW/sqlite        5.459m ± 1%   5.400m ± 1%       ~ (p=0.398 n=20)
LintRW/snowflake     5.690m ± 1%   5.610m ± 1%       ~ (p=0.033 n=20)
LintRW/clickhouse    6.131m ± 1%   6.148m ± 1%       ~ (p=0.495 n=20)
LintRule/L007        2.755m ± 1%   2.766m ± 2%       ~ (p=0.398 n=20)
LintRule/L010        866.6µ ± 6%   918.9µ ± 6%       ~ (p=0.398 n=20)
geomean              6.310m        6.327m       +0.27%

                  │     base     │                head                 │
                  │     B/s      │     B/s       vs base               │
LintRW/postgresql   4.892Mi ± 1%   4.959Mi ± 2%       ~ (p=0.291 n=20)
LintRW/mysql        5.207Mi ± 1%   5.178Mi ± 1%       ~ (p=0.507 n=20)
LintRW/mariadb      5.293Mi ± 2%   5.283Mi ± 1%       ~ (p=0.473 n=20)
LintRW/sqlserver    5.679Mi ± 1%   5.689Mi ± 1%       ~ (p=0.804 n=20)
LintRW/oracle       6.123Mi ± 1%   6.170Mi ± 2%       ~ (p=0.163 n=20)
LintRW/sqlite       5.255Mi ± 1%   5.312Mi ± 1%       ~ (p=0.386 n=20)
LintRW/snowflake    5.484Mi ± 1%   5.560Mi ± 1%       ~ (p=0.025 n=20)
LintRW/clickhouse   5.293Mi ± 1%   5.279Mi ± 1%       ~ (p=0.432 n=20)
geomean             5.393Mi        5.418Mi       +0.47%

                   │      base      │                 head                  │
                   │      B/op      │     B/op      vs base                 │
LintParallel/rules   8.111Mi ± 0%     8.111Mi ± 0%       ~ (p=0.941 n=20)
LintParallel/lint    28.08Mi ± 0%     28.08Mi ± 0%       ~ (p=0.256 n=20)
LintRW/postgresql    3.089Mi ± 0%     3.089Mi ± 0%       ~ (p=0.060 n=20)
LintRW/mysql         3.880Mi ± 0%     3.880Mi ± 0%       ~ (p=0.223 n=20)
LintRW/mariadb       3.570Mi ± 0%     3.570Mi ± 0%       ~ (p=0.794 n=20)
LintRW/sqlserver     3.124Mi ± 0%     3.124Mi ± 0%       ~ (p=0.941 n=20)
LintRW/oracle        3.818Mi ± 0%     3.818Mi ± 0%       ~ (p=0.140 n=20)
LintRW/sqlite        3.385Mi ± 0%     3.385Mi ± 0%       ~ (p=0.693 n=20)
LintRW/snowflake     3.384Mi ± 0%     3.384Mi ± 0%       ~ (p=0.119 n=20)
LintRW/clickhouse    3.832Mi ± 0%     3.832Mi ± 0%       ~ (p=0.457 n=20)
LintRule/L007        92.98Ki ± 0%     92.98Ki ± 0%       ~ (p=1.000 n=20) ¹
LintRule/L010          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                           ²                 +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                   │     base      │                 head                 │
                   │   allocs/op   │  allocs/op   vs base                 │
LintParallel/rules   158.3k ± 0%     158.3k ± 0%       ~ (p=0.120 n=20)
LintParallel/lint    236.9k ± 0%     236.9k ± 0%       ~ (p=0.860 n=20)
LintRW/postgresql    26.50k ± 0%     26.50k ± 0%       ~ (p=0.451 n=20)
LintRW/mysql         32.41k ± 0%     32.41k ± 0%       ~ (p=0.605 n=20)
LintRW/mariadb       29.61k ± 0%     29.61k ± 0%       ~ (p=1.000 n=20) ¹
LintRW/sqlserver     25.62k ± 0%     25.62k ± 0%       ~ (p=1.000 n=20)
LintRW/oracle        31.61k ± 0%     31.61k ± 0%       ~ (p=1.000 n=20)
LintRW/sqlite        29.43k ± 0%     29.43k ± 0%       ~ (p=1.000 n=20)
LintRW/snowflake     28.80k ± 0%     28.80k ± 0%       ~ (p=0.155 n=20)
LintRW/clickhouse    32.89k ± 0%     32.89k ± 0%       ~ (p=0.407 n=20)
LintRule/L007        1.194k ± 0%     1.194k ± 0%       ~ (p=1.000 n=20) ¹
LintRule/L010         0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                          ²                -0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                  │    base     │                head                │
                  │  sec/stmt   │  sec/stmt    vs base               │
LintRW/postgresql   18.98µ ± 1%   18.72µ ± 2%       ~ (p=0.298 n=20)
LintRW/mysql        15.17µ ± 1%   15.27µ ± 1%       ~ (p=0.490 n=20)
LintRW/mariadb      15.17µ ± 2%   15.20µ ± 1%       ~ (p=0.461 n=20)
LintRW/sqlserver    19.70µ ± 1%   19.65µ ± 1%       ~ (p=0.825 n=20)
LintRW/oracle       17.63µ ± 1%   17.49µ ± 2%       ~ (p=0.189 n=20)
LintRW/sqlite       15.69µ ± 1%   15.52µ ± 1%       ~ (p=0.394 n=20)
LintRW/snowflake    17.40µ ± 1%   17.16µ ± 1%       ~ (p=0.032 n=20)
LintRW/clickhouse   15.56µ ± 1%   15.60µ ± 1%       ~ (p=0.499 n=20)
LintRule/L007       979.4n ± 1%   983.1n ± 2%       ~ (p=0.394 n=20)
LintRule/L010       308.1n ± 6%   326.7n ± 6%       ~ (p=0.394 n=20)
geomean             8.489µ        8.510µ       +0.24%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.337µ ± 2%   1.330µ ± 2%       ~ (p=0.113 n=20)
Tokenize/scratch/complex   3.460µ ± 2%   3.392µ ± 3%       ~ (p=0.087 n=20)
Tokenize/scratch/insert    696.4n ± 2%   685.4n ± 2%       ~ (p=0.063 n=20)
geomean                    1.477µ        1.457µ       -1.38%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      89.87Mi ± 2%   90.40Mi ± 2%       ~ (p=0.108 n=20)
Tokenize/scratch/complex   104.2Mi ± 2%   106.3Mi ± 3%       ~ (p=0.085 n=20)
Tokenize/scratch/insert    104.1Mi ± 2%   105.7Mi ± 2%       ~ (p=0.063 n=20)
geomean                    99.14Mi        100.5Mi       +1.40%

                         │     base     │                head                 │
                         │     B/op     │    B/op     vs base                 │
Tokenize/scratch/join      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                               ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                         │     base     │                head                 │
                         │  allocs/op   │ allocs/op   vs base                 │
Tokenize/scratch/join      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                               ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/ajitpratap0/GoSQLX/pkg/transform
                             │    base     │                head                │
                             │   sec/op    │   sec/op     vs base               │
TransformParse/simple_select   1.200µ ± 1%   1.209µ ± 1%       ~ (p=0.046 n=20)
TransformParse/join            4.474µ ± 1%   4.444µ ± 3%       ~ (p=0.597 n=20)
TransformParse/complex         12.52µ ± 2%   12.38µ ± 3%       ~ (p=0.301 n=20)
geomean                        4.066µ        4.051µ       -0.37%

                             │     base     │                 head                  │
                             │     B/op     │     B/op      vs base                 │
TransformParse/simple_select     816.0 ± 0%     816.0 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/join            1.836Ki ± 0%   1.836Ki ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/complex         5.023Ki ± 0%   5.023Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                        1.944Ki        1.944Ki       +0.00%
¹ all samples are equal

                             │    base    │                head                 │
                             │ allocs/op  │ allocs/op   vs base                 │
TransformParse/simple_select   5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/join            17.00 ± 0%   17.00 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/complex         49.00 ± 0%   49.00 ± 0%       ~ (p=1.000 n=20) ¹
geomean                        16.09        16.09       +0.00%
¹ all samples are equal

Run, raw data and benchstat

Test and others added 2 commits November 3, 2026 23:40
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
…and comment fixes [W1-04]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 19s —— View job


Review of PR #940: reserve the parse stack once before tokenizing [W1-04]

I read the diff for stackgrow.go, the tokscratch and parser call sites, the CHANGELOG and gate-suite.txt. I did not build or run tests. Test results below come from the PR description and the perf-gate comment.

Verdict: no blocking issues. The change is small, internal-only, and the amd64 gate confirms it.

Evidence

  • The amd64 layout-randomized gate passes. ParseFresh/simple_select is -58.7%, join -43.6% and complex -23.5%. allocs/op and B/op are unchanged.
  • The A/A' control is clean: 0 of 52 benchmarks moved more than 3%.
  • ParseE2E/delete is +1.63% (p=0.001, flagged "ok"). The call is about 1 ns, and that benchmark takes about 1.2 µs on a warm goroutine. That is roughly the size of the delta, so it is consistent with the cost of Reserve on the already-grown path. It is not a regression, but it is the one place the cost shows up.
  • Instructions/op rise by 0.05-0.40%, well under the 1.5% bound.

Code

  • internal/stackgrow/stackgrow.go
    • The approach is sound. The 12 KiB frame sits behind a never-true package-level guard, so the prologue forces morestack and the array is never zeroed.
    • //go:noinline on both functions is correct, and passing &buf to a noinline callee keeps the frame in place.
    • The cost of this design is that it depends on never not being constant-folded. Your mutation check (never as const fails T1) covers that.
  • Call sites
    • tokscratch.Bytes/String/StringContext plus the four public pipelines and three parser-core backstops give good coverage. Your corpus check reports 2996 of 2999 statements.
    • The parser backstop comments are accurate but long. They sit on one line and repeat verbatim at parseTokens and parseContextTokens. Consider a shared doc comment on stackgrow.Reserve and a short // backstop; see stackgrow at the call sites.

Non-blocking suggestions

  1. Gate suite widening (M2).
    • The diff shows gate-suite.txt already includes insert|update|delete for ParseFresh. That contradicts the PR body, which says the suite was not widened. Please update the PR body or revert the line, whichever you intended.
    • The gate ran on head 58e1cfa, and its table has no ParseFresh/insert, update or delete rows. The suite change landed after that run (it looks like it came with the later commit 21e8482), so the new cells have not been gated yet. I recommend keeping them and checking the next gate run.
  2. CHANGELOG. The entry says "insert, update, delete: see the PR". That leaves the numbers outside the repo. Fill them in from the next gate run or drop the parenthetical. The same entry also repeats the memory trade-off that appears under "Behavior changes", so you could shorten one of the two.
  3. Toolchain fragility. The landing size (16 KiB) depends on the runtime's stack-growth policy. Check that T2 and T4 failure messages point at the package doc comment, so a Go upgrade failure is diagnosable.
  4. Memory. Short-lived goroutines that parse once now hold at least 16 KiB. The CHANGELOG records this. A line in docs/PERFORMANCE_METHODOLOGY.md would help server users find it.
  5. Test flakes. The wall-clock ratio flakes in your task check run came from machine load, not this change. Please confirm a clean CI test:race run before merge.

Security

No new input handling, no unsafe, and the reserved size is a compile-time constant. No concerns.

Test coverage

Good. It includes entry-point enumeration (NoUnreserved), a corpus coverage check, budget tests, and the mutation checks listed in the PR.

Overall this is good to merge once the CHANGELOG numbers and the PR-body/gate-suite.txt mismatch are resolved.

… [W1-04]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 21s —— View job


Review of PR #940: reserve the parse stack once before tokenizing [W1-04]

I read stackgrow.go, the CHANGELOG, test.yml and gate-suite.txt diffs. I did not build or run tests.

Verdict: no blocking issues. The change is small and internal-only. The latest amd64 gate run confirms the gain.

Evidence

  • ParseFresh: simple_select -58.6%, join -43.7%, complex -23.8%, and insert/update/delete -57% to -66%. allocs/op and B/op are unchanged. The A/A' control is clean (0 of 55 benchmarks moved more than 3%).
  • Instructions/op: +0.06% to +0.45%, under the 1.5% bound.
  • ParseRW/clickhouse/medium is flagged slower (+3.22%, p<0.001).
    • ParseRW/sqlite/medium is +1.86% (p=0.001) and not flagged.
    • The ParseRW benchmarks measure the warm path, where Reserve is a no-op after the first call. A 3% slowdown on a roughly 90 µs benchmark is about 3 µs, far more than a 1 ns call.
    • The A/A' control for clickhouse moved +2.18%, and its spread is 3.49%, so layout noise is the likely cause.
    • The gate is in shadow mode. Re-run once and confirm it is not reproducible before merge.

Code

  • stackgrow.go is sound. The frame is above the big-frame threshold, the guard never is a package variable so it can't be folded, and //go:noinline is on both functions. The array is never zeroed. The mutation checks in the PR body cover the const-folding case.
  • Toolchain fragility: the 16 KiB landing depends on runtime stack-growth policy. Make sure the T2 and T4 failure messages point at the package doc comment, so a Go upgrade failure is easy to diagnose.
  • Comments at call sites: the parser backstop comments repeat across parseTokens and parseContextTokens. A short // backstop; see stackgrow would do, with the detail kept on Reserve.

Items to resolve before merge

  1. PR body vs gate-suite.txt. The diff widens BenchmarkParseFresh to simple_select|join|complex|insert|update|delete. The PR body says the suite was not widened (decision M2). Update the body, or revert the line. The latest gate run does show the insert/update/delete rows, so I'd keep the change and fix the body.
  2. CHANGELOG now has the real numbers for insert, update and delete. The memory trade-off appears in both the Performance entry and the Behavior changes entry. You could shorten one of them.
  3. .github/workflows/test.yml (CI step):
    • The diff shows the step running -run 'AllocBudget|StackBudget|StackReserve' ./pkg/gosqlx only.
    • The PR body says the step also covers ./internal/stackgrow.
    • Either add the package to the step, or fix the body. The stackgrow tests would otherwise only run in the general test job.
  4. Memory docs. Short-lived goroutines that parse once now hold at least 16 KiB. A note in docs/PERFORMANCE_METHODOLOGY.md or the user docs would help server users find this. It is recorded in the CHANGELOG.
  5. Race run. Confirm a clean CI test:race run. The flakes in the PR body came from machine load and are unrelated to this change.

Security

No new input handling, no unsafe, and the reserved size is a compile-time constant. No concerns.

Test coverage

Good. It covers entry-point enumeration (NoUnreserved), the corpus coverage check (2996 of 2999), the budget tests and the mutation checks.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 28s —— View job


Review of PR #940: reserve the parse stack once before tokenizing [W1-04]

I read the full diff against origin/main. I did not build or run tests. Test and perf numbers come from the PR body and the gate comment.

Verdict: no blocking issues. The earlier review notes about the CHANGELOG placeholder and the gate-suite.txt mismatch are resolved in the current head.

Resolved since the earlier reviews

  • The CHANGELOG now carries real amd64 gate numbers for all six ParseFresh cells, with no "see the PR" placeholder.
  • scripts/perf/gate-suite.txt includes insert|update|delete, and the PR body now says so. The latest gate run shows those cells at -57%, -59% and -66%.
  • The memory trade-off is documented under "Behavior changes".

Code

  • internal/stackgrow/stackgrow.go is sound.
    • The 12 KiB array sits behind a never-written package variable, so it is never zeroed, and the two //go:noinline markers keep the frame.
    • The cost is a call and a compare, about 1 ns when the stack is already large.
    • The package is internal, so no API surface changes.
  • Call sites are consistent: the tokscratch entry points, the four public pipelines and the three parser-core backstops.
  • Pooling and concurrency: nothing here touches pooled objects, so the CLAUDE.md pooling and race rules are unaffected.

Non-blocking suggestions

  1. Duplicated comment. The ~250-character one-line comment is repeated verbatim in parseTokens and parseContextTokens (pkg/sql/parser/parser.go). It also has nested parentheses. Put the caveat in the Reserve doc comment and use // backstop; see stackgrow.Reserve at the call sites. parseWithRecovery and ParseBytesWithTokens have no comment, so they would benefit too.
  2. Entry-reserve sites. Analyze, LintString, parseWithRecoveryForDialect and ParseBytesWithTokens call Reserve and then reach tokscratch or the parser core, which call it again. That is intentional and costs about 1 ns. A short note such as // entry reserve; tokscratch and parser core also backstop would stop a later cleanup from removing the "redundant" call. The NoUnreserved test probably catches that, but the note is cheaper.
  3. Toolchain fragility. The 16 KiB landing depends on the runtime's stack growth policy. The package doc covers Windows and -race. Make sure the T2 and T4 failure messages point at that doc, so a future Go upgrade is easy to diagnose.
  4. Server memory note. A goroutine-per-request server that parses once now holds at least 16 KiB per goroutine. A line in docs/PERFORMANCE_METHODOLOGY.md or the user docs would help those users find it. Worker pools see no change.
  5. CI scope. The non-race step runs -run 'AllocBudget|StackBudget|StackReserve' ./pkg/gosqlx. The PR body says ./pkg/gosqlx ./internal/stackgrow. The diff in .github/workflows/test.yml only lists ./pkg/gosqlx, so the internal/stackgrow tests do not run in that step. They still run in the normal go test ./... job. Add ./internal/stackgrow to the step if the intent is to run them without -race, or update the PR body.
  6. Open perf items, as the PR body already says.
    • ParseFresh/complex reaches -23.3%, below the W1-04 target of -40%. Re-check the W3-06 trigger against this result.
    • ParseRW/clickhouse/medium was flagged at +3.2% in one run (A/A' +2.2%, Low-power list). It did not reproduce in the other run. It is worth a look if it shows up again.
    • The LintRW/postgresql allocs/op change (+1 in 34k) is pool jitter.

Security

No new input handling, no unsafe, and the reserved size is a compile-time constant. No concerns.

Test coverage

Good. It includes entry-point enumeration (NoUnreserved), a real-world corpus coverage check (2996/2999), the stack budget tests and the mutation checks. Confirm a clean CI task test:race run before merge, because the timing-ratio flakes you saw locally came from machine load.

Overall this looks good to merge once CI is green.

This branch was successfully deployed

1 active deployment
Preview — e92c2487 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-cd documentation Improvements or additions to documentation linter lsp parser

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant