Skip to content

perf(lsp): cheaper semanticTokens [W6-11] - #948

Merged
ajitpratap0 merged 7 commits into
mainfrom
perf/w6-11-cheaper-semantic-tokens
Nov 7, 2026
Merged

ajitpratap0 merged 7 commits into
mainfrom
perf/w6-11-cheaper-semantic-tokens

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

W6-11 (LSP-2): highlight.Spans keeps lexeme/comment/line-start buffers in a pooled scratch and case-folds each lexeme once through a shared buffer; semantic-token encoding is leaner. No public API or behaviour change; a highlight golden plus an LSP reference test pin output byte-identical. Follow-ups in this round: merged origin/main (CHANGELOG conflict with #896 resolved, both entries kept under the single ### Performance heading), renamed the misnamed highlight_bench_test.go to concurrency_test.go, and documented the intentionally duplicated emit block in encodeSemanticTokens.

Test Plan

  • Post-merge head: task check, task test:cbinding, go test -count=1 ./pkg/compatibility/..., staticcheck on pkg/lsp and pkg/highlight: all pass. One unrelated timing flake (internal/scaletest TestAssertLinearGrowthSlowQuadraticFailsWithinBudget, race shard 2/4) passed on CI rerun.
  • Commits: 3a2b47f (implementation), 24e5b38 (cleanup), 5500f85 (rename/comment), plus two origin/main merges. Not tests-first (plan review ran after implementation).
  • Layout-randomized matrix A/B on amd64 (run 41743109325, 10 rounds x 10 layouts, EPYC 9V45, head 5500f85; the 20-layout run 41707629339 hit the 230 min step timeout and produced no verdict). Target rows, sec/op / B/op / allocs:
    • Spans: -12.15% (p<0.001), B/op -84.8%, allocs 252 -> 3
    • LSP/semanticTokens/30KB postgresql/mysql/sqlserver: -13.3 / -13.9 / -14.3% (p<0.001), B/op -26.4 / -26.8 / -26.0%, allocs ~2000 -> ~20
    • LSP/semanticTokens/1KB: -24.9 / -34.4 / -37.3%; 300KB: -12.4 / -17.7 / -18.1%
    • The PLAN target (30KB <= -15%) is narrowly MISSED on 30KB (about -13 to -14%, mysql/sqlserver within the noise of -15%); reported, not hidden. Every moved row is significant, allocs/op and B/op fall everywhere, none of the 1038 benchmarks is flagged low-power for these rows.
    • The report's overall "REGRESSION" flag comes from Scan*/Lint*/WASMBridge rows (packages this PR does not touch): the base was main at 6e86a6b (perf(linter): aliasing + 4 visitor rules onto Context.Nodes() [W6-05b] #953), newer than this branch's merge base, so those rows show main's own changes. No pkg/lsp or pkg/highlight row regressed.
  • Local A/B (M4, shared and indicative only; task bench:ab BASE=origin/main ROUNDS=6 LAYOUTS=4, ./pkg/lsp BenchmarkLSP/semanticTokens, head ca27d16): 30KB sec/op -21.4 / -18.8 / -18.9% (pg/mysql/sqlserver, p=0.002), B/op -26.4 / -26.8 / -26.0%, allocs 2032 -> 13, 1856 -> 14, 1946 -> 14; 1KB B/op -63 to -79%; 300KB allocs -99.7%. Full output: scratch ab-local.log.
  • Tests-first evidence: highlight_golden_test.go, concurrency_test.go, semantic_tokens_ref_test.go, semantic_tokens_test.go and testdata/goldens/highlight.sha256, copied onto the unchanged merge base, all pass. The new golden therefore proves the output is byte-identical to before.
  • Mutations run (all caught by a failing test unless noted): (a) drop the in-span CRLF unit -> TestEncodeSemanticTokensMatchesReference, TestSemanticTokens_LineEndings, FuzzEncodeSemanticTokens, FuzzEncodeSemanticTokensSpans fail; (b) fold non-ASCII values ASCII-only -> TestFoldUpperMatchesToUpper, TestSpansUnicodeCaseFolding fail; (j) drop clear(sc.lexemes[:cap(sc.lexemes)]) from resetScratch (the real retention mutation: pooled scratch would pin document strings) -> TestScratchClearedAfterRealRun and TestPutScratchDropsReferences fail; note storing h.sc.lexemes = out[:0] is harmless (resetScratch clears to cap, same backing array) and is not a retention bug; plan mutations (c)-(i) were not re-run in this round. Also added ASCII control/DEL bytes to the 200k random differential; a mutation making DEL count 2 UTF-16 units in the in-span ASCII branch of handler.go now fails TestEncodeSemanticTokensMatchesReference.
  • Authoritative amd64 perf-gate (sticky comment, base 8889402, head 4445089 = merge ref of ca27d16, 20 layouts per arm, A/A' run): its gated suite has no LSP/highlight benchmark. It flags one row, PoolGetPut/serial +5.43% (p<0.001, 0 allocs, A/A' -0.05%). This PR changes no code in pkg/gosqlx or anything PoolGetPut exercises, and the same row read -3.46% on the previous head, so this is reported as layout/runner noise rather than hidden; no other gated row moved and allocs/op and B/op did not rise anywhere.

Breaking Changes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

Gate record

Amd64 matrix run 41743109325: 30KB semanticTokens -13.3/-13.9/-14.3% (p<0.001), which MISSES the PLAN target of <= -15% (reported, not hidden; still a significant gain so PLAN 5.3 rule 5 does not apply). Deterministic thresholds hold: 30KB B/op -26%, allocs ~-99%, 1KB B/op -63 to -79%, Spans allocs 252->3. BenchmarkLSP is not yet in scripts/perf/gate-suite.txt, so the time verdict comes from matrix mode, not the gated suite. Layout-randomized gate on ca27d16 flagged PoolGetPut/serial +5.43%: this benchmark only touches tokenizer/parser/AST Get/Put which this PR does not change, and the row swings without cause on unrelated PRs (#954 -3.29%, #945 -3.53%); re-run on head 3307dc8 (merge ref 5703e86, base 8889402, 20 layouts/arm, AMD EPYC 9V45): Perf gate PASS, PoolGetPut/serial -0.41% (p=0.552, listed under Low power), no gated regression, allocs/op and B/op unchanged. All CI checks green, mergeState CLEAN.

…ared case folding [W6-11]

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 7, 2026 8:54am UTC

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

I read the diff for pkg/highlight/highlight.go, scratch.go and pkg/lsp/handler.go. I did not run the build or tests, because the Bash tool needed approval for them here. This is a static review, so rely on your task check and the perf gate for runtime behaviour.

Overall: The change looks correct and well contained. I found no blocking issues.

What I checked

  • Pooled scratch (scratch.go):
    • putScratch clears lexemes[:lexHigh] and the full parens capacity before pooling. A pooled scratch therefore doesn't pin the document through the folded strings.
    • lexHigh is reset to 0 when resolve reallocates, which is right.
    • The oversize caps are applied per buffer.
    • Reslicing to [:0] after the clear is fine.
  • foldUpper:
    • Non-ASCII values fall back to strings.ToUpper, so the result is exact.
    • ASCII values share one strings.Builder. Grow(total) is called up front, so the builder never reallocates and the b.String()[off:] substrings stay valid.
    • The comment about the radix check (l.up == "0", isRadixSuffix) is correct.
  • semanticEncodings as [256]: Category is uint8 (pkg/highlight/category.go:24), so indexing by sp.Category is always in range. This is safe and removes the map lookup.
  • encodeSemanticTokens:
    • The inlined fast path for ASCII keeps the previous behaviour: \r\n is consumed as a unit, and a span start that lands mid-\r\n is handled the same way.
    • Zero-length segments are skipped.
    • The reference test pins the output against the old implementation.
  • run(): tkz.Comments and the scratch tokens are only used before PutTokenizer (via defer), which matches the stated lifetime contract.

Suggestions (non-blocking)

  1. Duplicated emit code. The append(data, dl, dc, l, …) plus prevLine/prevChar update appears twice in encodeSemanticTokens. The old closure was clearer. If the inlining is what saves the allocations, a small helper may still inline, so check with -gcflags=-m. If it doesn't inline, the duplication is acceptable. A one-line comment saying why it is inlined would help.
  2. Fragile defer ordering. In resolve, classify and indexLines, the defer writes the slice back into the scratch. This is correct today, but an early return added later could skip the write-back. In classify, st.parens is also written back after the loop. A short comment would help. Alternatively, write back explicitly at the end of each function.
  3. Concurrency test. Spans is pool-backed and used concurrently by the LSP. Make sure one of the new tests runs it from many goroutines under -race, with inputs of mixed sizes that cross the retention caps. The race_on/race_off test files suggest this is probably covered.
  4. Perf-gate verdict. The PR body says the CI perf-gate verdict is pending. Per the repo's "measure, don't assume" rule, please add the numbers before merge. The reported 2,032 → 13 allocs/op is from a local M4 probe only.
  5. Golden hash churn. testdata/goldens/highlight.sha256 adds 258 lines, and Taskfile.yml and scripts/goldens-diff.sh are wired up. Confirm that CI runs goldens-diff for the new highlight set.

Security: I found no concerns. There is no unsafe code, and the pooled buffers are cleared of document references before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test against the old encoder, a fold test and benchmarks.
| Branch: perf/w6-11-cheaper-semantic-tokens

@github-actions github-actions Bot added documentation Improvements or additions to documentation performance ci-cd lsp 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 79774ddc8f16, head 408f38a17a27. 20 interleaved rounds, 20 randomized code layouts per arm (-randlayout seeds 423574034092..423574034111; A/A' 423574034112..423574034131), benchtime 100ms, -cpu 1, GOMEMLIMIT 64MiB, pinned to CPU 3 (SMT siblings 2-3) with taskset.

Runner CPU: AMD EPYC 7763 64-Core Processor (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.

  • no gated metric moved past its threshold

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.43% 0.213 +0.28% 1.05% +0.00% 5 → 5 ok
ParseE2E/select_where +0.23% 0.552 +0.41% 0.99% +0.00% 18 → 18 ok
ParseE2E/join +0.60% 0.050 +0.30% 0.60% +0.00% 17 → 17 ok
ParseE2E/aggregate -0.33% 0.695 -0.86% 2.76% +0.00% 18 → 18 ok
ParseE2E/subquery -0.02% 0.989 +0.68% 1.44% +0.00% 14 → 14 ok
ParseE2E/cte +0.24% 0.695 +1.04% 2.23% +0.00% 21 → 21 ok
ParseE2E/window +0.52% 0.140 -0.16% 1.15% +0.00% 11 → 11 ok
ParseE2E/complex +0.61% 0.277 +0.83% 1.90% +0.00% 49 → 49 ok
ParseE2E/insert +0.39% 0.525 +0.07% 1.86% +0.00% 17 → 17 ok
ParseE2E/update +0.26% 0.607 -0.26% 1.12% +0.00% 13 → 13 ok
ParseE2E/delete +0.33% 0.432 +0.25% 1.68% +0.00% 5 → 5 ok
ParseRW/postgresql/medium -0.45% 0.327 +0.51% 1.21% +0.00% 646 → 646 ok
ParseRW/mysql/medium +0.82% 0.043 +0.65% 0.92% +0.00% 368 → 368 ok
ParseRW/mariadb/medium +0.41% 0.211 +0.93% 0.71% +0.01% 528 → 528 ok
ParseRW/sqlserver/medium +0.70% 0.242 +1.26% 0.83% +0.00% 927 → 927 ok
ParseRW/oracle/medium +0.44% 0.204 +0.65% 0.80% +0.00% 1090 → 1090 ok
ParseRW/sqlite/medium +0.76% 0.165 +0.39% 2.03% +0.00% 347 → 347 ok
ParseRW/snowflake/medium +0.30% 0.383 +0.46% 0.83% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium -0.75% 0.301 -1.52% 3.01% +0.00% 383 → 383 ok
ParseFresh/simple_select +0.59% 0.218 +0.11% 1.56% +0.00% 6 → 6 ok
ParseFresh/join +0.64% 0.126 +0.14% 1.75% +0.00% 18 → 18 ok
ParseFresh/complex +0.60% 0.140 +0.62% 0.88% +0.00% 50 → 50 ok
PoolGetPut/serial -0.21% 0.358 -0.05% 0.91% +0.00% 0 → 0 ok
Tokenize/scratch/join +0.27% 0.607 +1.48% 2.18% +0.00% 0 → 0 ok
Tokenize/scratch/complex -0.07% 0.860 +1.27% 2.72% +0.00% 0 → 0 ok
Tokenize/scratch/insert -0.11% 0.818 +0.80% 1.88% +0.00% 0 → 0 ok
Normalize/simple_select -0.04% 0.903 -0.39% 0.89% +0.00% 7 → 7 ok
Normalize/join +0.23% 0.351 +0.40% 1.15% +0.00% 23 → 23 ok
Normalize/complex -0.17% 0.675 +0.73% 1.38% +0.00% 68 → 68 ok
TransformParse/simple_select +0.65% 0.323 +0.86% 1.83% +0.00% 5 → 5 ok
TransformParse/join +0.05% 0.957 +0.23% 1.03% +0.00% 17 → 17 ok
TransformParse/complex +0.80% 0.204 +0.32% 1.38% +0.00% 49 → 49 ok
LintParallel/rules +1.38% 0.201 -0.11% 4.41% +0.00% 68931.5 → 68934 ok
LintParallel/lint -1.01% 0.040 -0.77% 1.27% -0.00% 133239 → 133239 ok
LintRW/postgresql -0.01% 0.989 +0.13% 0.86% +0.00% 14798 → 14798 ok
LintRW/mysql +0.78% 0.265 +0.60% 1.91% -0.00% 18252 → 18252 ok
LintRW/mariadb +0.38% 0.512 -0.62% 2.25% -0.00% 16562 → 16562 ok
LintRW/sqlserver +0.24% 0.565 +0.24% 1.62% +0.00% 14526 → 14526 ok
LintRW/oracle -0.30% 0.429 -0.33% 0.79% -0.00% 17804 → 17804 ok
LintRW/sqlite -0.34% 0.327 +0.32% 1.12% +0.00% 16521 → 16521 ok
LintRW/snowflake -0.12% 0.841 -0.42% 2.18% -0.00% 16113 → 16113 ok
LintRW/clickhouse -1.14% 0.183 -1.28% 2.18% -0.00% 18657 → 18657 ok
LintRule/L007 +0.14% 0.862 -0.22% 3.07% +0.00% 1194 → 1194 ok
LintRule/L010 +0.00% 0.978 +0.03% 1.21% +0.00% 0 → 0 ok
ParseError/syntax_mid/parse +0.02% 0.946 +0.37% 0.61% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate +0.00% 1.000 +0.82% 0.79% +0.00% 12 → 12 ok
ParseError/syntax_end/parse -0.02% 0.989 +0.57% 1.57% +0.00% 16 → 16 ok
ParseError/syntax_end/validate +0.82% 0.239 +0.85% 1.70% +0.00% 16 → 16 ok
ParseError/tokenize/parse +0.35% 0.626 -0.50% 2.26% +0.00% 4 → 4 ok
ParseError/tokenize/validate -0.36% 0.490 +0.18% 1.74% +0.00% 21 → 21 ok
ParseError/long_prefix/parse +1.01% 0.242 +1.43% 1.20% +0.01% 219 → 219 ok
ParseError/long_prefix/validate +1.60% 0.231 +1.72% 1.44% +0.00% 219 → 219 ok
geomean +0.23% 0.583 +0.29%

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 52 benchmarks moved more than 3%; 0 would have been flagged by the time rule; suite geomean +0.29%, p 0.086.

Low power

1 benchmark(s) have under 80% estimated power to be flagged on their own for a +5% slowdown (their spread across layouts is too wide for 20 layouts at the Benjamini-Hochberg level alpha/m = 0.000192). A slowdown below the last column usually goes unflagged there; instructions/op, allocs/op and the suite geomean still guard them.

benchmark spread power at +5% slowdown caught 80% of the time
LintParallel/rules 4.41% 38% +6.7%

Power is estimated with the normal approximation for the U test (Noether): power = Phi(ln(1+shift)/s * sqrt(K/2) * sqrt(3/pi) - z(1 - alpha/(2m))), with s the spread and K the layouts per arm.

Instructions per operation (cmd/perfdriver)

case instructions/op status
e2e/aggregate +0.00% ok
e2e/complex +0.01% ok
e2e/cte -0.07% ok
e2e/delete -0.01% ok
e2e/insert +0.00% ok
e2e/join -0.02% ok
e2e/select_where -0.02% ok
e2e/simple_select +0.06% ok
e2e/subquery -0.09% ok
e2e/update +0.01% ok
e2e/window +0.08% ok
rw/postgresql/medium +0.02% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select -1.26% 0.049
ParseE2E/select_where -0.42% 0.290
ParseE2E/join -0.78% 0.315
ParseE2E/aggregate +0.10% 0.821
ParseE2E/subquery -0.04% 1.000
ParseE2E/cte -0.10% 0.880
ParseE2E/window +0.81% 0.162
ParseE2E/complex +0.13% 0.970
ParseE2E/insert +0.84% 0.241
ParseE2E/update +1.52% 0.019
ParseE2E/delete -0.40% 0.597
ParseRW/postgresql/medium -0.14% 0.529
ParseRW/mysql/medium +0.16% 0.912
ParseRW/mariadb/medium -0.03% 0.971
ParseRW/sqlserver/medium +0.47% 0.393
ParseRW/oracle/medium -0.48% 0.247
ParseRW/sqlite/medium +0.02% 1.000
ParseRW/snowflake/medium -0.00% 1.000
ParseRW/clickhouse/medium +0.21% 0.684
ParseFresh/simple_select +0.23% 0.684
ParseFresh/join +0.47% 0.353
ParseFresh/complex +0.14% 0.971
PoolGetPut/serial -0.49% 0.045
Tokenize/scratch/join -0.03% 1.000
Tokenize/scratch/complex -0.13% 0.796
Tokenize/scratch/insert -0.52% 0.684
Normalize/simple_select +0.06% 0.880
Normalize/join -0.56% 0.762
Normalize/complex +0.74% 0.684
TransformParse/simple_select +0.52% 0.364
TransformParse/join +1.56% 0.105
TransformParse/complex +0.03% 0.853
LintParallel/rules -0.11% 0.912
LintParallel/lint +0.46% 0.190
LintRW/postgresql +0.32% 0.190
LintRW/mysql -0.11% 0.631
LintRW/mariadb +0.50% 0.165
LintRW/sqlserver +0.34% 0.393
LintRW/oracle +0.66% 0.089
LintRW/sqlite +0.27% 0.280
LintRW/snowflake -0.61% 0.143
LintRW/clickhouse +0.39% 0.280
LintRule/L007 -0.03% 1.000
LintRule/L010 +0.02% 0.912
ParseError/syntax_mid/parse -0.00% 1.000
ParseError/syntax_mid/validate +0.16% 0.650
ParseError/syntax_end/parse -0.70% 0.450
ParseError/syntax_end/validate +0.45% 0.344
ParseError/tokenize/parse -0.09% 1.000
ParseError/tokenize/validate +0.09% 0.971
ParseError/long_prefix/parse +0.25% 0.853
ParseError/long_prefix/validate +1.42% 0.123
geomean +0.12%
Environment
date:        2026-11-07T08:57:04Z
base:        79774ddc8f168ec4d5bf887760503de33262bddd
head:        working tree of 408f38a17a2759719f91b22ead6f50ebdae32e4d
cpu model:   AMD EPYC 7763 64-Core Processor (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 423574034092..423574034111 (base, head), 423574034112..423574034131 (A/A')
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        8.13, 2.85, 1.03
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:                              AMD EPYC 7763 64-Core Processor
Thread(s) per core:                      2
Core(s) per socket:                      2
Socket(s):                               1
Hypervisor vendor:                       Microsoft
L1d cache:                               64 KiB (2 instances)
L1i cache:                               64 KiB (2 instances)
L2 cache:                                1 MiB (2 instances)
L3 cache:                                32 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: AMD EPYC 7763 64-Core Processor                
                        │    base     │                head                │
                        │   sec/op    │   sec/op     vs base               │
Normalize/simple_select   2.336µ ± 1%   2.332µ ± 0%       ~ (p=0.899 n=20)
Normalize/join            7.110µ ± 1%   7.129µ ± 1%       ~ (p=0.351 n=20)
Normalize/complex         19.75µ ± 1%   19.74µ ± 1%       ~ (p=0.673 n=20)
geomean                   6.896µ        6.897µ       +0.03%

                        │     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.625µ ± 1%   1.639µ ± 1%       ~ (p=0.213 n=20)
ParseE2E/select_where             3.442µ ± 1%   3.449µ ± 1%       ~ (p=0.551 n=20)
ParseE2E/join                     4.603µ ± 1%   4.631µ ± 1%       ~ (p=0.048 n=20)
ParseE2E/aggregate                3.463µ ± 4%   3.456µ ± 3%       ~ (p=0.693 n=20)
ParseE2E/subquery                 3.097µ ± 1%   3.105µ ± 1%       ~ (p=0.984 n=20)
ParseE2E/cte                      4.395µ ± 1%   4.404µ ± 1%       ~ (p=0.693 n=20)
ParseE2E/window                   2.898µ ± 1%   2.916µ ± 1%       ~ (p=0.140 n=20)
ParseE2E/complex                  12.15µ ± 1%   12.19µ ± 1%       ~ (p=0.277 n=20)
ParseE2E/insert                   2.155µ ± 1%   2.162µ ± 1%       ~ (p=0.525 n=20)
ParseE2E/update                   1.918µ ± 1%   1.922µ ± 1%       ~ (p=0.606 n=20)
ParseE2E/delete                   1.234µ ± 1%   1.236µ ± 1%       ~ (p=0.433 n=20)
ParseRW/postgresql/medium         168.6µ ± 1%   167.7µ ± 1%       ~ (p=0.327 n=20)
ParseRW/mysql/medium              96.21µ ± 1%   97.22µ ± 1%       ~ (p=0.043 n=20)
ParseRW/mariadb/medium            136.5µ ± 0%   137.0µ ± 1%       ~ (p=0.211 n=20)
ParseRW/sqlserver/medium          271.6µ ± 1%   274.0µ ± 1%       ~ (p=0.242 n=20)
ParseRW/oracle/medium             321.9µ ± 1%   323.5µ ± 1%       ~ (p=0.204 n=20)
ParseRW/sqlite/medium             72.95µ ± 1%   73.56µ ± 1%       ~ (p=0.165 n=20)
ParseRW/snowflake/medium          189.4µ ± 1%   189.1µ ± 1%       ~ (p=0.383 n=20)
ParseRW/clickhouse/medium         90.15µ ± 2%   89.62µ ± 2%       ~ (p=0.301 n=20)
ParseFresh/simple_select          7.027µ ± 1%   7.088µ ± 1%       ~ (p=0.218 n=20)
ParseFresh/join                   11.34µ ± 1%   11.40µ ± 1%       ~ (p=0.126 n=20)
ParseFresh/complex                20.41µ ± 1%   20.54µ ± 1%       ~ (p=0.140 n=20)
PoolGetPut/serial                 67.00n ± 1%   66.81n ± 0%       ~ (p=0.358 n=20)
ParseError/syntax_mid/parse       2.413µ ± 0%   2.417µ ± 1%       ~ (p=0.941 n=20)
ParseError/syntax_mid/validate    2.151µ ± 2%   2.159µ ± 1%       ~ (p=0.995 n=20)
ParseError/syntax_end/parse       2.778µ ± 1%   2.777µ ± 3%       ~ (p=0.984 n=20)
ParseError/syntax_end/validate    2.622µ ± 3%   2.623µ ± 2%       ~ (p=0.240 n=20)
ParseError/tokenize/parse         918.2n ± 2%   919.3n ± 2%       ~ (p=0.625 n=20)
ParseError/tokenize/validate      2.812µ ± 1%   2.795µ ± 1%       ~ (p=0.490 n=20)
ParseError/long_prefix/parse      60.11µ ± 1%   60.74µ ± 2%       ~ (p=0.242 n=20)
ParseError/long_prefix/validate   55.97µ ± 1%   57.40µ ± 4%       ~ (p=0.231 n=20)
geomean                           9.475µ        9.510µ       +0.37%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            15.26Mi ± 1%   15.13Mi ± 1%       ~ (p=0.224 n=20)
ParseE2E/select_where             28.53Mi ± 1%   28.49Mi ± 1%       ~ (p=0.560 n=20)
ParseE2E/join                     26.11Mi ± 1%   25.94Mi ± 1%       ~ (p=0.050 n=20)
ParseE2E/aggregate                23.13Mi ± 4%   23.18Mi ± 3%       ~ (p=0.703 n=20)
ParseE2E/subquery                 25.25Mi ± 1%   25.19Mi ± 1%       ~ (p=0.973 n=20)
ParseE2E/cte                      26.90Mi ± 1%   26.86Mi ± 1%       ~ (p=0.703 n=20)
ParseE2E/window                   30.93Mi ± 1%   30.74Mi ± 1%       ~ (p=0.136 n=20)
ParseE2E/complex                  29.68Mi ± 1%   29.56Mi ± 1%       ~ (p=0.268 n=20)
ParseE2E/insert                   33.64Mi ± 1%   33.52Mi ± 1%       ~ (p=0.525 n=20)
ParseE2E/update                   27.35Mi ± 1%   27.28Mi ± 1%       ~ (p=0.625 n=20)
ParseE2E/delete                   40.20Mi ± 1%   40.12Mi ± 1%       ~ (p=0.449 n=20)
ParseRW/postgresql/medium         21.28Mi ± 1%   21.39Mi ± 1%       ~ (p=0.298 n=20)
ParseRW/mysql/medium              28.13Mi ± 1%   27.84Mi ± 1%       ~ (p=0.041 n=20)
ParseRW/mariadb/medium            25.97Mi ± 0%   25.87Mi ± 1%       ~ (p=0.213 n=20)
ParseRW/sqlserver/medium          24.50Mi ± 1%   24.28Mi ± 1%       ~ (p=0.234 n=20)
ParseRW/oracle/medium             26.04Mi ± 1%   25.91Mi ± 1%       ~ (p=0.229 n=20)
ParseRW/sqlite/medium             28.32Mi ± 1%   28.08Mi ± 2%       ~ (p=0.167 n=20)
ParseRW/snowflake/medium          26.85Mi ± 1%   26.88Mi ± 1%       ~ (p=0.365 n=20)
ParseRW/clickhouse/medium         25.00Mi ± 2%   25.15Mi ± 2%       ~ (p=0.304 n=20)
ParseError/syntax_mid/parse       18.97Mi ± 0%   18.94Mi ± 1%       ~ (p=0.909 n=20)
ParseError/syntax_mid/validate    21.29Mi ± 2%   21.21Mi ± 1%       ~ (p=0.973 n=20)
ParseError/syntax_end/parse       13.39Mi ± 1%   13.39Mi ± 4%       ~ (p=1.000 n=20)
ParseError/syntax_end/validate    14.19Mi ± 3%   14.18Mi ± 2%       ~ (p=0.229 n=20)
ParseError/tokenize/parse         47.78Mi ± 2%   47.72Mi ± 1%       ~ (p=0.625 n=20)
ParseError/tokenize/validate      15.60Mi ± 1%   15.70Mi ± 1%       ~ (p=0.499 n=20)
ParseError/long_prefix/parse      38.43Mi ± 1%   38.03Mi ± 2%       ~ (p=0.240 n=20)
ParseError/long_prefix/validate   41.27Mi ± 1%   40.25Mi ± 4%       ~ (p=0.229 n=20)
geomean                           25.59Mi        25.49Mi       -0.36%

                                │      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.91Ki ± 0%     65.91Ki ± 0%       ~ (p=0.834 n=20)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=0.256 n=20)
ParseRW/mariadb/medium            60.61Ki ± 0%     60.61Ki ± 0%       ~ (p=0.013 n=20)
ParseRW/sqlserver/medium          83.68Ki ± 0%     83.68Ki ± 0%       ~ (p=0.772 n=20)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=0.753 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=0.661 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) ¹
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.085 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) ¹
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   14.05µ ± 1%   13.98µ ± 1%       ~ (p=0.327 n=20)
ParseRW/mysql/medium        12.03µ ± 1%   12.15µ ± 1%       ~ (p=0.043 n=20)
ParseRW/mariadb/medium      15.17µ ± 0%   15.23µ ± 1%       ~ (p=0.214 n=20)
ParseRW/sqlserver/medium    12.93µ ± 1%   13.05µ ± 1%       ~ (p=0.242 n=20)
ParseRW/oracle/medium       13.41µ ± 1%   13.48µ ± 1%       ~ (p=0.204 n=20)
ParseRW/sqlite/medium       12.16µ ± 1%   12.26µ ± 1%       ~ (p=0.165 n=20)
ParseRW/snowflake/medium    14.57µ ± 1%   14.55µ ± 1%       ~ (p=0.380 n=20)
ParseRW/clickhouse/medium   12.88µ ± 2%   12.80µ ± 2%       ~ (p=0.301 n=20)
geomean                     13.36µ        13.40µ       +0.30%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                   │     base     │                head                 │
                   │    sec/op    │    sec/op     vs base               │
LintParallel/rules   19.79m ±  3%   20.55m ±  8%       ~ (p=0.201 n=20)
LintParallel/lint    38.37m ±  1%   38.00m ±  1%       ~ (p=0.040 n=20)
LintRW/postgresql    5.047m ±  1%   5.050m ±  1%       ~ (p=0.989 n=20)
LintRW/mysql         5.395m ±  1%   5.409m ±  1%       ~ (p=0.265 n=20)
LintRW/mariadb       4.902m ±  2%   4.931m ±  1%       ~ (p=0.512 n=20)
LintRW/sqlserver     4.666m ±  1%   4.673m ±  1%       ~ (p=0.565 n=20)
LintRW/oracle        5.608m ±  1%   5.585m ±  1%       ~ (p=0.429 n=20)
LintRW/sqlite        4.509m ±  1%   4.489m ±  1%       ~ (p=0.327 n=20)
LintRW/snowflake     4.772m ±  3%   4.764m ±  3%       ~ (p=0.841 n=20)
LintRW/clickhouse    5.349m ±  2%   5.253m ±  2%       ~ (p=0.183 n=20)
LintRule/L007        2.722m ±  3%   2.737m ±  2%       ~ (p=0.862 n=20)
LintRule/L010        701.9µ ± 26%   702.5µ ± 26%       ~ (p=0.974 n=20)
geomean              5.376m         5.384m        +0.14%

                  │     base     │                head                 │
                  │     B/s      │     B/s       vs base               │
LintRW/postgresql   5.612Mi ± 0%   5.608Mi ± 1%       ~ (p=0.984 n=20)
LintRW/mysql        6.065Mi ± 1%   6.046Mi ± 1%       ~ (p=0.261 n=20)
LintRW/mariadb      6.223Mi ± 2%   6.185Mi ± 1%       ~ (p=0.465 n=20)
LintRW/sqlserver    6.638Mi ± 1%   6.628Mi ± 1%       ~ (p=0.533 n=20)
LintRW/oracle       7.081Mi ± 1%   7.110Mi ± 1%       ~ (p=0.432 n=20)
LintRW/sqlite       6.366Mi ± 1%   6.394Mi ± 1%       ~ (p=0.364 n=20)
LintRW/snowflake    6.537Mi ± 3%   6.547Mi ± 3%       ~ (p=0.878 n=20)
LintRW/clickhouse   6.070Mi ± 2%   6.180Mi ± 2%       ~ (p=0.185 n=20)
geomean             6.311Mi        6.323Mi       +0.20%

                   │      base      │                 head                  │
                   │      B/op      │     B/op      vs base                 │
LintParallel/rules   5.559Mi ± 0%     5.559Mi ± 0%       ~ (p=0.151 n=20)
LintParallel/lint    18.19Mi ± 0%     18.19Mi ± 0%       ~ (p=0.931 n=20)
LintRW/postgresql    1.992Mi ± 0%     1.992Mi ± 0%       ~ (p=0.815 n=20)
LintRW/mysql         2.538Mi ± 0%     2.538Mi ± 0%       ~ (p=0.578 n=20)
LintRW/mariadb       2.312Mi ± 0%     2.312Mi ± 0%       ~ (p=0.053 n=20)
LintRW/sqlserver     1.996Mi ± 0%     1.996Mi ± 0%       ~ (p=0.995 n=20)
LintRW/oracle        2.469Mi ± 0%     2.469Mi ± 0%       ~ (p=0.473 n=20)
LintRW/sqlite        2.205Mi ± 0%     2.206Mi ± 0%       ~ (p=0.358 n=20)
LintRW/snowflake     2.203Mi ± 0%     2.203Mi ± 0%       ~ (p=0.465 n=20)
LintRW/clickhouse    2.476Mi ± 0%     2.476Mi ± 0%       ~ (p=0.194 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   68.93k ± 0%     68.93k ± 0%       ~ (p=0.183 n=20)
LintParallel/lint    133.2k ± 0%     133.2k ± 0%       ~ (p=0.295 n=20)
LintRW/postgresql    14.80k ± 0%     14.80k ± 0%       ~ (p=1.000 n=20)
LintRW/mysql         18.25k ± 0%     18.25k ± 0%       ~ (p=1.000 n=20)
LintRW/mariadb       16.56k ± 0%     16.56k ± 0%       ~ (p=0.748 n=20)
LintRW/sqlserver     14.53k ± 0%     14.53k ± 0%       ~ (p=1.000 n=20)
LintRW/oracle        17.80k ± 0%     17.80k ± 0%       ~ (p=1.000 n=20) ¹
LintRW/sqlite        16.52k ± 0%     16.52k ± 0%       ~ (p=1.000 n=20)
LintRW/snowflake     16.11k ± 0%     16.11k ± 0%       ~ (p=1.000 n=20) ¹
LintRW/clickhouse    18.66k ± 0%     18.66k ± 0%       ~ (p=0.231 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   16.55µ ±  1%   16.56µ ±  1%       ~ (p=0.984 n=20)
LintRW/mysql        13.03µ ±  1%   13.06µ ±  1%       ~ (p=0.265 n=20)
LintRW/mariadb      12.90µ ±  2%   12.98µ ±  1%       ~ (p=0.512 n=20)
LintRW/sqlserver    16.84µ ±  1%   16.87µ ±  1%       ~ (p=0.569 n=20)
LintRW/oracle       15.24µ ±  1%   15.18µ ±  1%       ~ (p=0.418 n=20)
LintRW/sqlite       12.96µ ±  1%   12.90µ ±  1%       ~ (p=0.327 n=20)
LintRW/snowflake    14.59µ ±  3%   14.57µ ±  3%       ~ (p=0.841 n=20)
LintRW/clickhouse   13.58µ ±  2%   13.33µ ±  2%       ~ (p=0.183 n=20)
LintRule/L007       967.5n ±  3%   973.2n ±  2%       ~ (p=0.846 n=20)
LintRule/L010       249.5n ± 26%   249.7n ± 26%       ~ (p=0.984 n=20)
geomean             7.321µ         7.313µ        -0.11%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.460µ ± 2%   1.468µ ± 2%       ~ (p=0.606 n=20)
Tokenize/scratch/complex   3.841µ ± 2%   3.832µ ± 2%       ~ (p=0.857 n=20)
Tokenize/scratch/insert    738.2n ± 1%   737.3n ± 1%       ~ (p=0.815 n=20)
geomean                    1.606µ        1.607µ       +0.06%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      82.30Mi ± 2%   81.86Mi ± 2%       ~ (p=0.616 n=20)
Tokenize/scratch/complex   93.86Mi ± 2%   94.08Mi ± 2%       ~ (p=0.841 n=20)
Tokenize/scratch/insert    98.18Mi ± 1%   98.30Mi ± 1%       ~ (p=0.799 n=20)
geomean                    91.19Mi        91.14Mi       -0.06%

                         │     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.540µ ± 1%   1.554µ ± 2%       ~ (p=0.324 n=20)
TransformParse/join            4.950µ ± 1%   4.962µ ± 1%       ~ (p=0.952 n=20)
TransformParse/complex         13.07µ ± 1%   13.15µ ± 1%       ~ (p=0.204 n=20)
geomean                        4.636µ        4.663µ       +0.58%

                             │     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

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

I read the full diff of pkg/highlight/highlight.go, scratch.go and pkg/lsp/handler.go. I did not run the build or tests, so this is a static review. The perf-gate comment shows no gated regressions, with allocs/op and B/op unchanged across the suite.

Verdict: The change is correct and well contained. I found one minor issue and a few nits.

Minor issue: stale lexemes can pin memory in the pool

  • putScratch runs clear(sc.lexemes), which only clears up to len.
  • Spans retries run after a tokenize error. If the retry produces fewer lexemes than the first attempt, the entries between the new length and the old length keep their up strings.
  • Those strings may reference the source document, depending on how tokscratch.String hands back values, or the shared fold builder. They then sit in a pooled scratch.
  • The case is rare, but the fix is a single line: clear(sc.lexemes[:cap(sc.lexemes)]). It matches what you already do for parens.
  • In resolve, the reallocation branch drops the old backing array, so it is fine.

What I checked and found correct

  • foldUpper:
    • Grow(total) is called up front, so the b.String()[off:] substrings stay valid.
    • Non-ASCII values fall back to strings.ToUpper, so the result is exact.
    • Values with no lowercase ASCII keep the original string. That is fine, because lexemes are only used before run returns.
  • semanticEncodings [256]: Category is uint8, so indexing is always in range.
  • encodeSemanticTokens:
    • The inlined version keeps the same CRLF, mid-\r\n, zero-length-segment and UTF-16 behaviour as the old closures.
    • The reference test pins this against the old encoder.
  • classify/indexLines: the defer write-backs are correct today.

Nits (non-blocking)

  1. The emit block (append plus the prevLine/prevChar update) is duplicated in encodeSemanticTokens. A comment saying it is inlined on purpose for allocations would help. A small helper may still inline, so it is worth checking with -gcflags=-m.
  2. The defer write-backs in resolve, classify and indexLines break silently if someone adds an early return and forgets the pattern. A one-line comment would protect against that.
  3. A test that calls Spans concurrently from many goroutines, with inputs on both sides of the retention caps and with a mid-document tokenize error to exercise the retry path, would cover the pooling and the issue above. The race_on/race_off files suggest some of this may already exist.
  4. The -15% time target is unproven. The perf gate has no semanticTokens benchmark yet, and the PR body says the lane-B PR will add one. The allocs/op drop (2,032 → 13) comes from a local M4 probe only. I'd keep the merge held until that benchmark lands, as the PR body already says.
  5. Please confirm CI runs goldens-diff for the new highlight set. Taskfile.yml and scripts/goldens-diff.sh are wired up, but I haven't seen it run.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared before reuse, apart from the retry edge case above.

Tests: Coverage is good: a byte-identical golden, an LSP reference test, a fold test and benchmarks.
| Branch: perf/w6-11-cheaper-semantic-tokens

…ing [W6-11]

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 33s —— View job


Review of PR #948: cheaper semanticTokens [W6-11]

I read pkg/highlight/scratch.go and the run/resolve path in highlight.go. I did not build or run the tests, so this is a static review. The latest perf-gate comment shows no gated regressions, with allocs/op and B/op unchanged on the gated suite.

Verdict: I found no blocking issues. I checked the earlier "stale lexemes pin memory" concern from the previous review and it does not apply to the current code.

Why the putScratch clear is sufficient

  • putScratch clears sc.lexemes[:len].
  • lexemes is only written in resolve, and it is written back through the defer, so len always covers every entry written in that call.
  • The tokenize error path in run returns before resolve runs, so a failed attempt writes no lexemes.
  • classify returns no error, so nothing can fail after resolve.
  • Every returned scratch therefore has a zeroed backing array. A later call that uses fewer entries only needs to clear what it wrote.
  • Clearing sc.lexemes[:cap(sc.lexemes)] would be harmless belt-and-braces, but it is not needed. A one-line comment on this invariant would help future editors.

Non-blocking suggestions

  1. defer write-backs. The defer write-backs in resolve, classify and indexLines are correct today. A short comment would protect against someone adding an early return later.
  2. Duplicated emit code. The emit block in encodeSemanticTokens is duplicated. Keep it if inlining is what removes the allocations, and say so in a comment. -gcflags=-m would show whether a helper would inline.
  3. Perf evidence. The perf gate has no semanticTokens benchmark. The 2,032 → 13 allocs/op and B/op numbers come from a local M4 probe only. The -15% time target is unproven on amd64, as the PR body says. Keep the merge held until the lane-B benchmark lands.
  4. Goldens. Confirm CI runs goldens-diff for the new highlight set. The Taskfile.yml and scripts/goldens-diff.sh changes wire it up.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test against the old encoder, a fold test, a concurrency test and benchmarks.
| Branch: perf/w6-11-cheaper-semantic-tokens

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

I read the diff for pkg/highlight/highlight.go, scratch.go and pkg/lsp/handler.go. I did not build or run tests, so this is a static review.

Verdict: I found no blocking issues. The change is correct and well contained.

Checked and correct

  • putScratch / lexeme lifetime:
    • lexemes is only written in resolve, and the defer write-back keeps len covering every entry written.
    • The tokenize-error path returns before resolve, so a failed attempt writes no lexemes. clear(sc.lexemes) is therefore enough. The earlier "stale lexemes" concern doesn't apply.
    • putScratch is deferred in Spans, so it runs after the last use of the up strings.
  • foldUpper:
    • Grow(total) up front means the builder never reallocates, so the b.String()[off:] substrings stay valid.
    • Non-ASCII values fall back to strings.ToUpper, so the result is exact.
    • The l.up == "0" and isRadixSuffix(next.up) comments are sound.
  • semanticEncodings [256]: Category is uint8, so indexing can't go out of range, and the map lookup is gone.
  • encodeSemanticTokens:
    • The inlined ASCII fast path keeps the CRLF handling, the mid-\r\n span start, zero-length skipping and UTF-16 counting from the old closures.
    • The reference test pins this against the old encoder.
    • The "written out twice" comment is now present.

Non-blocking suggestions

  1. Add a one-line comment on the invariant in putScratch that len(lexemes) covers everything written. It would stop a future edit from breaking it.
  2. The defer write-backs in resolve, classify and indexLines are correct today. A short comment would protect against someone adding an early return later.
  3. Spans is pool-backed and the LSP calls it concurrently. concurrency_test.go is there, so please confirm it covers inputs on both sides of the retention caps (maxRetainedLexemes and the others), and a mid-document tokenize error that exercises the retry path.
  4. Confirm CI runs goldens-diff for the new highlight set. The Taskfile and script changes wire it up, but I haven't seen it run.

Performance

  • The PR body reports Spans -12.2% and 30KB semanticTokens -13 to -14%, with allocs/op falling from ~2000 to ~20.
  • The body also says the PLAN target of -15% on 30KB is narrowly missed, and reports it openly, which fits the repo's "measure, don't assume" rule.
  • The PR-run perf gate showed no regressions on its suite. As the body notes, that suite has no LSP/highlight benchmark, so it is only no-regression evidence.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared of document references before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test against the old encoder, a fold test, a concurrency test and benchmarks.
| Branch: perf/w6-11-cheaper-semantic-tokens

…4 figures in CHANGELOG [W6-11]

Merge origin/main; split resetScratch from putScratch so tests never touch a
pooled object; add a driven case that fails if resolve stores out[:0].

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 6, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

This is a static review of the current head (ca27d16) against origin/main. I read pkg/highlight/scratch.go and the highlight.go diff. I did not build or run tests.

Verdict: No blocking issues. The change is correct and contained.

Checked and correct

  • Scratch clearing. resetScratch now clears lexemes[:cap] and parens[:cap]. That covers the stale-lexeme concern from the earlier reviews. The per-buffer retention caps are applied independently.
  • foldUpper.
    • Grow(total) runs before any write, so the builder never reallocates and the b.String()[off:] substrings stay valid.
    • Non-ASCII values fall back to strings.ToUpper, so the result is exact.
    • Values with no lowercase ASCII keep their original string. That is fine because up is only used inside run, and the scratch is cleared before it is pooled.
  • Write-back via defer. The defer write-backs in resolve, classify and indexLines are correct today.
  • Tokenizer lifetime. run uses the scratch tokens and tkz.Comments only before PutTokenizer.
  • Failed attempts. A failed tokenize attempt returns before resolve, so it leaves nothing in lexemes.

Findings (minor)

  1. Stale doc comment in scratch.go.
    • The comment block above resetScratch begins with putScratch clears document references…, followed by resetScratch drops every retained reference…. It looks like the putScratch comment was left attached to the wrong function.
    • putScratch itself has no doc comment.
    • Split the block so each function has its own comment.
  2. Invariant comment. Add a short comment in resolve, classify and indexLines saying the defer write-back must stay in sync with the buffer on every return path. This guards against a future early return.
  3. CI confirmation. I can't see from here that CI runs goldens-diff for the new highlight set. The Taskfile.yml and scripts/goldens-diff.sh changes wire it up, so please confirm it ran green.

Performance

  • The matrix A/B in the PR body shows Spans -12.2% and 30KB semanticTokens -13 to -14%. Allocs/op fall from about 2000 to about 20.
  • The PR body states openly that the -15% plan target is narrowly missed on 30KB. That matches the repo's "measure, don't assume" rule.
  • The PR-run perf gate shows no regressions. Its gated suite has no LSP/highlight benchmark, so it is only no-regression evidence.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared of document references before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test against the old encoder, a fold test, a concurrency test and benchmarks.
| Branch: perf/w6-11-cheaper-semantic-tokens

…erential [W6-11]

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 7, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

This is a static review of the current head. I read pkg/highlight/scratch.go in full. I did not build or run the tests. My checks of the other files rest on the earlier review passes and on the PR body.

Verdict: No blocking issues. The change is correct and contained.

Checked and correct

  • Scratch clearing. resetScratch clears lexemes[:cap] and parens[:cap] before it truncates. A pooled scratch therefore holds no document strings, and the earlier stale-lexeme concern is closed. The retention caps apply to each buffer independently.
  • comments and lineStarts. They are not cleared before truncation. That is fine if Span holds no pointers or strings. Please confirm that stays true, because adding a string field to Span later would make these buffers pin documents.
  • foldUpper. Grow(total) runs before any write, so the builder never reallocates and the substrings stay valid. Non-ASCII values fall back to strings.ToUpper, so the result is exact.
  • semanticEncodings [256]. Category is uint8, so indexing cannot go out of range.
  • encodeSemanticTokens. The inlined emit block keeps the CRLF, mid-\r\n, zero-length-segment and UTF-16 behaviour. The differential test against the old encoder pins this, including the ASCII control and DEL bytes added in the last commit.

Minor findings

  1. Misplaced doc comment in scratch.go:39-42.
    • The putScratch comment ("putScratch clears document references before pooling…") is merged into the resetScratch comment. putScratch has no doc comment of its own.
    • Split them: keep the "drops every retained reference" text on resetScratch, and give putScratch a one-line comment.
  2. defer write-backs. The defer write-backs in resolve, classify and indexLines are correct today. A one-line comment would guard against a future early return that skips the write-back.
  3. CI confirmation. I can't see from here that CI ran goldens-diff for the new highlight set. Please confirm it ran green.

Performance

  • The matrix A/B in the PR body shows Spans -12.2% and 30KB semanticTokens -13 to -14%. Allocs/op fall from about 2000 to about 20.
  • The PR states openly that the -15% target is narrowly missed on 30KB.
  • The perf-gate comment flags PoolGetPut/serial at +5.43%. The PR attributes this to layout and runner noise, since the PR touches nothing that benchmark exercises.
    • The A/A' control reads -0.05%.
    • The benchmark swings without cause on unrelated PRs.
    • Allocs/op is 0 → 0.
    • Instructions/op is flat.
    • That is consistent with noise, but it is worth one rerun on the final head to be sure.
  • The gated suite has no LSP/highlight benchmark, so it only shows that nothing regressed. Adding BenchmarkLSP to scripts/perf/gate-suite.txt would give the time claim a permanent gate.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared of document references before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test and fuzz tests, a fold test, a concurrency test, and mutation checks that the PR body describes.
| Branch: perf/w6-11-cheaper-semantic-tokens

@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

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


Review of PR #948: cheaper semanticTokens [W6-11]

This is a static review of the current head. I read pkg/highlight/scratch.go in full. I did not build or run tests. My checks of the other files rest on the earlier review passes and on the PR body.

Verdict: No blocking issues. The change is correct and contained.

Checked and correct

  • Scratch clearing. resetScratch clears lexemes[:cap] and parens[:cap] before it truncates. That closes the earlier stale-lexeme concern. The retention caps apply to each buffer independently.
  • comments and lineStarts. They are not cleared before truncation. That is fine while Span holds no pointers or strings. If a string field is ever added to Span, these buffers would pin documents. A comment on the struct would guard that.
  • foldUpper, semanticEncodings [256] and encodeSemanticTokens. I did not re-read these this round. The earlier reviews found them correct, and the PR's differential, fuzz and mutation tests pin them.

Minor findings

  1. Misplaced doc comment (scratch.go:39-42).
    • The putScratch comment ("putScratch clears document references before pooling…") runs straight into the resetScratch comment.
    • putScratch has no doc comment of its own.
    • Keep the "drops every retained reference" text on resetScratch, and give putScratch a one-line comment.
    • The first line of the scratch struct comment ("Lexemes are cleared on return") is also slightly out of date, since parens is cleared as well.
  2. defer write-backs. The write-backs in resolve, classify and indexLines are correct today. A one-line comment would guard against a future early return that skips the write-back.
  3. CI. I can't confirm from here that goldens-diff ran green for the new highlight set. Please check it.

Performance

  • The matrix A/B shows Spans -12.2% and 30KB semanticTokens -13 to -14%, with allocs/op falling from about 2000 to about 20. The PR reports the narrow miss on the -15% target openly.
  • The latest perf-gate run (head 5703e865) passes. PoolGetPut/serial reads -0.41%, so the earlier +5.43% was noise. No gated B/op or allocs/op change.
  • The gated suite has no LSP/highlight benchmark, so it only shows nothing regressed. Adding BenchmarkLSP to scripts/perf/gate-suite.txt would give the time claim a permanent gate.

Security: No concerns. There is no unsafe code, and pooled buffers are cleared of document references before reuse.

Tests: Coverage is good: a byte-identical golden, an LSP reference test with fuzz and a control/DEL differential, a fold test, a concurrency test, and the mutation checks in the PR body.

@ajitpratap0
ajitpratap0 merged commit ff7790d into main Nov 7, 2026
52 checks passed
@ajitpratap0
ajitpratap0 deleted the perf/w6-11-cheaper-semantic-tokens branch November 7, 2026 09:44

This branch was successfully deployed

1 active deployment
Preview — 76ad336d Deployed Nov 7, 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 lsp performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant