Skip to content

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

Open
ajitpratap0 wants to merge 4 commits into
mainfrom
perf/w6-11-cheaper-semantic-tokens
Open

ajitpratap0 wants to merge 4 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.
  • PR-run perf gate on the earlier head (e578343, 20 layouts, A/A' 0/52): PASS, 0 regressions, geomean -0.05% (p 0.602); its gated suite has no LSP/highlight benchmark, so it is only the no-regression evidence.

Breaking Changes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

…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 4, 2026 2:15am 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 6e86a6b8b9b1, head faebce305ce2. 20 interleaved rounds, 20 randomized code layouts per arm (-randlayout seeds 417593793372..417593793391; A/A' 417593793392..417593793411), benchtime 100ms, -cpu 1, GOMEMLIMIT 64MiB, pinned to CPU 3 (SMT siblings 2-3) with taskset.

Runner CPU: AMD EPYC 9V74 80-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.

  • 1 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.12% 0.968 +0.08% 2.14% +0.00% 5 → 5 ok
ParseE2E/select_where +0.07% 0.860 -0.39% 1.28% +0.00% 18 → 18 ok
ParseE2E/join -0.07% 0.871 -0.05% 4.68% +0.00% 17 → 17 ok
ParseE2E/aggregate -1.58% 0.164 -0.87% 3.83% +0.00% 18 → 18 ok
ParseE2E/subquery -0.51% 0.234 -0.86% 1.35% +0.00% 14 → 14 ok
ParseE2E/cte -0.21% 0.698 -0.51% 2.06% +0.00% 21 → 21 ok
ParseE2E/window -1.16% 0.105 -1.61% 1.63% +0.00% 11 → 11 ok
ParseE2E/complex -0.25% 0.543 -0.63% 1.48% +0.00% 49 → 49 ok
ParseE2E/insert -0.16% 0.543 -0.65% 0.93% +0.00% 17 → 17 ok
ParseE2E/update -0.37% 0.176 -0.18% 0.99% +0.00% 13 → 13 ok
ParseE2E/delete +0.20% 0.776 -0.04% 2.80% +0.00% 5 → 5 ok
ParseRW/postgresql/medium -0.25% 0.301 +0.15% 0.73% +0.00% 646 → 646 ok
ParseRW/mysql/medium -0.66% 0.091 -0.12% 0.91% +0.00% 368 → 368 ok
ParseRW/mariadb/medium -1.25% 0.221 -2.78% 2.08% -0.00% 528 → 528 ok
ParseRW/sqlserver/medium -0.30% 0.387 -0.08% 0.94% +0.00% 927 → 927 ok
ParseRW/oracle/medium +0.01% 0.978 -0.37% 1.84% +0.00% 1090 → 1090 ok
ParseRW/sqlite/medium +0.12% 0.779 +0.21% 1.20% +0.00% 347 → 347 ok
ParseRW/snowflake/medium -2.68% 0.072 -1.67% 2.75% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium -0.28% 0.358 -0.50% 1.14% +0.00% 383 → 383 ok
ParseFresh/simple_select +0.48% 0.314 +0.01% 1.12% +0.00% 6 → 6 ok
ParseFresh/join +0.26% 0.394 +0.07% 1.19% +0.00% 18 → 18 ok
ParseFresh/complex +0.00% 1.000 +0.07% 0.69% +0.00% 50 → 50 ok
PoolGetPut/serial -3.46% <0.001 +0.10% 0.38% +0.00% 0 → 0 faster
Tokenize/scratch/join -0.09% 0.745 -0.37% 1.99% +0.00% 0 → 0 ok
Tokenize/scratch/complex -0.49% 0.402 -0.48% 1.75% +0.00% 0 → 0 ok
Tokenize/scratch/insert -0.35% 0.698 -1.26% 4.00% +0.00% 0 → 0 ok
Normalize/simple_select +0.21% 0.735 +0.45% 1.06% +0.00% 7 → 7 ok
Normalize/join -0.36% 0.314 +0.07% 1.32% +0.00% 23 → 23 ok
Normalize/complex +1.35% 0.133 +1.52% 2.85% +0.00% 68 → 68 ok
TransformParse/simple_select +0.08% 0.839 +0.00% 2.21% +0.00% 5 → 5 ok
TransformParse/join -0.09% 0.745 +0.23% 1.19% +0.00% 17 → 17 ok
TransformParse/complex -0.27% 0.383 +0.37% 1.18% +0.00% 49 → 49 ok
LintParallel/rules -0.36% 0.698 +0.46% 2.24% +0.00% 68934 → 68934 ok
LintParallel/lint +0.46% 0.429 +0.74% 1.19% -0.00% 147509.5 → 147509 ok
LintRW/postgresql -0.18% 0.718 +0.06% 1.63% +0.00% 16337 → 16337 ok
LintRW/mysql -0.08% 0.862 -0.13% 1.06% -0.00% 20353 → 20353 ok
LintRW/mariadb +0.29% 0.698 +0.19% 1.24% -0.00% 18485 → 18484 ok
LintRW/sqlserver +0.62% 0.678 -0.03% 0.97% +0.00% 15940 → 15940 ok
LintRW/oracle -0.04% 0.989 +0.77% 1.99% +0.00% 19674 → 19674 ok
LintRW/sqlite +0.32% 0.602 +0.65% 1.71% +0.00% 18302 → 18302 ok
LintRW/snowflake -0.13% 0.738 +0.18% 1.39% +0.00% 17765 → 17765 ok
LintRW/clickhouse +0.69% 0.265 -0.18% 2.06% -0.00% 20645 → 20645 ok
LintRule/L007 +0.95% 0.192 -0.09% 1.58% +0.00% 1194 → 1194 ok
LintRule/L010 -0.18% 0.602 -0.07% 9.67% +0.00% 0 → 0 ok
ParseError/syntax_mid/parse -0.55% 0.218 -0.51% 1.28% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate -0.59% 0.285 -0.64% 2.30% +0.00% 12 → 12 ok
ParseError/syntax_end/parse -0.66% 0.218 -0.17% 1.74% +0.00% 16 → 16 ok
ParseError/syntax_end/validate -0.34% 0.552 -0.49% 3.25% +0.00% 16 → 16 ok
ParseError/tokenize/parse +0.74% 0.417 +0.20% 2.01% +0.00% 4 → 4 ok
ParseError/tokenize/validate +1.30% 0.110 +0.68% 1.76% +0.00% 21 → 21 ok
ParseError/long_prefix/parse +0.39% 0.588 +0.61% 2.31% +0.00% 219 → 219 ok
ParseError/long_prefix/validate +0.01% 0.989 +0.06% 1.20% +0.00% 219 → 219 ok
geomean -0.19% 0.478 -0.15%

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.15%, p 0.081.

Low power

4 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
LintRule/L010 9.67% 2% +15.4%
ParseE2E/join 4.68% 31% +7.2%
Tokenize/scratch/insert 4.00% 52% +6.1%
ParseE2E/aggregate 3.83% 58% +5.8%

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.05% ok
e2e/complex +0.00% ok
e2e/cte -0.06% ok
e2e/delete +0.14% ok
e2e/insert +0.03% ok
e2e/join -0.01% ok
e2e/select_where -0.02% ok
e2e/simple_select +0.20% ok
e2e/subquery -0.03% ok
e2e/update +0.07% ok
e2e/window +0.02% ok
rw/postgresql/medium +0.02% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select +0.61% 0.325
ParseE2E/select_where +0.74% 0.449
ParseE2E/join +1.68% 0.096
ParseE2E/aggregate +0.08% 0.853
ParseE2E/subquery +0.62% 0.307
ParseE2E/cte +1.03% 0.140
ParseE2E/window +1.72% 0.190
ParseE2E/complex -0.53% 0.631
ParseE2E/insert -1.07% 0.307
ParseE2E/update -1.18% 0.173
ParseE2E/delete +0.67% 0.449
ParseRW/postgresql/medium +0.21% 0.684
ParseRW/mysql/medium -0.92% 0.165
ParseRW/mariadb/medium +0.15% 0.971
ParseRW/sqlserver/medium -0.79% 0.165
ParseRW/oracle/medium -1.82% 0.190
ParseRW/sqlite/medium -0.98% 0.218
ParseRW/snowflake/medium -1.50% 0.105
ParseRW/clickhouse/medium -0.18% 0.796
ParseFresh/simple_select +0.46% 0.450
ParseFresh/join +0.05% 0.791
ParseFresh/complex -0.16% 0.650
PoolGetPut/serial -3.53% 0.011
Tokenize/scratch/join +0.00% 0.909
Tokenize/scratch/complex +0.10% 0.650
Tokenize/scratch/insert -0.07% 0.970
Normalize/simple_select +0.05% 0.940
Normalize/join +0.22% 0.623
Normalize/complex -0.31% 0.684
TransformParse/simple_select -0.12% 0.910
TransformParse/join -0.69% 0.449
TransformParse/complex -0.56% 0.280
LintParallel/rules +2.12% 0.353
LintParallel/lint +0.17% 0.853
LintRW/postgresql +0.83% 0.529
LintRW/mysql +0.32% 0.739
LintRW/mariadb +0.86% 0.393
LintRW/sqlserver +0.82% 0.436
LintRW/oracle +0.40% 0.631
LintRW/sqlite +1.07% 0.436
LintRW/snowflake -0.14% 0.971
LintRW/clickhouse +0.41% 0.631
LintRule/L007 +0.03% 1.000
LintRule/L010 -0.21% 0.684
ParseError/syntax_mid/parse +0.73% 0.623
ParseError/syntax_mid/validate -0.93% 0.597
ParseError/syntax_end/parse -0.63% 0.623
ParseError/syntax_end/validate -0.97% 0.496
ParseError/tokenize/parse +2.31% 0.190
ParseError/tokenize/validate -0.50% 0.796
ParseError/long_prefix/parse -0.28% 0.796
ParseError/long_prefix/validate -0.53% 0.631
geomean -0.01%
Environment
date:        2026-11-04T02:15:57Z
base:        6e86a6b8b9b1e7dbb91268edd0972fbe357e773b
head:        working tree of faebce305ce2e1ebd3eeb67f3af2c02b2410d2f0
cpu model:   AMD EPYC 9V74 80-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 417593793372..417593793391 (base, head), 417593793392..417593793411 (A/A')
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        7.83, 2.53, 0.90
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 9V74 80-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:                                2 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 9V74 80-Core Processor                
                        │    base     │                head                │
                        │   sec/op    │   sec/op     vs base               │
Normalize/simple_select   1.894µ ± 1%   1.900µ ± 1%       ~ (p=0.733 n=20)
Normalize/join            5.597µ ± 1%   5.564µ ± 1%       ~ (p=0.314 n=20)
Normalize/complex         14.77µ ± 2%   14.96µ ± 3%       ~ (p=0.133 n=20)
geomean                   5.389µ        5.407µ       +0.34%

                        │     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.248µ ± 1%   1.239µ ± 1%       ~ (p=0.963 n=20)
ParseE2E/select_where             2.715µ ± 1%   2.719µ ± 1%       ~ (p=0.857 n=20)
ParseE2E/join                     3.801µ ± 3%   3.830µ ± 4%       ~ (p=0.867 n=20)
ParseE2E/aggregate                2.846µ ± 3%   2.781µ ± 3%       ~ (p=0.163 n=20)
ParseE2E/subquery                 2.474µ ± 1%   2.461µ ± 1%       ~ (p=0.234 n=20)
ParseE2E/cte                      3.376µ ± 1%   3.365µ ± 2%       ~ (p=0.698 n=20)
ParseE2E/window                   2.272µ ± 1%   2.233µ ± 2%       ~ (p=0.104 n=20)
ParseE2E/complex                  9.790µ ± 3%   9.748µ ± 1%       ~ (p=0.542 n=20)
ParseE2E/insert                   1.842µ ± 1%   1.837µ ± 1%       ~ (p=0.542 n=20)
ParseE2E/update                   1.648µ ± 1%   1.642µ ± 1%       ~ (p=0.175 n=20)
ParseE2E/delete                   979.2n ± 5%   980.9n ± 5%       ~ (p=0.774 n=20)
ParseRW/postgresql/medium         114.4µ ± 0%   114.2µ ± 0%       ~ (p=0.301 n=20)
ParseRW/mysql/medium              68.67µ ± 1%   68.26µ ± 1%       ~ (p=0.091 n=20)
ParseRW/mariadb/medium            95.97µ ± 2%   94.62µ ± 3%       ~ (p=0.221 n=20)
ParseRW/sqlserver/medium          194.1µ ± 1%   194.0µ ± 0%       ~ (p=0.387 n=20)
ParseRW/oracle/medium             238.6µ ± 1%   238.4µ ± 1%       ~ (p=0.973 n=20)
ParseRW/sqlite/medium             55.04µ ± 2%   55.00µ ± 1%       ~ (p=0.779 n=20)
ParseRW/snowflake/medium          133.7µ ± 2%   131.5µ ± 4%       ~ (p=0.072 n=20)
ParseRW/clickhouse/medium         65.50µ ± 1%   65.20µ ± 1%       ~ (p=0.358 n=20)
ParseFresh/simple_select          5.545µ ± 1%   5.564µ ± 1%       ~ (p=0.314 n=20)
ParseFresh/join                   8.223µ ± 1%   8.238µ ± 1%       ~ (p=0.394 n=20)
ParseFresh/complex                15.63µ ± 1%   15.66µ ± 1%       ~ (p=1.000 n=20)
PoolGetPut/serial                 58.96n ± 0%   56.95n ± 0%  -3.40% (p=0.000 n=20)
ParseError/syntax_mid/parse       1.919µ ± 1%   1.912µ ± 1%       ~ (p=0.218 n=20)
ParseError/syntax_mid/validate    1.708µ ± 2%   1.696µ ± 1%       ~ (p=0.285 n=20)
ParseError/syntax_end/parse       2.087µ ± 2%   2.085µ ± 1%       ~ (p=0.218 n=20)
ParseError/syntax_end/validate    2.050µ ± 3%   2.050µ ± 2%       ~ (p=0.551 n=20)
ParseError/tokenize/parse         700.5n ± 2%   706.5n ± 2%       ~ (p=0.417 n=20)
ParseError/tokenize/validate      2.102µ ± 1%   2.126µ ± 2%       ~ (p=0.110 n=20)
ParseError/long_prefix/parse      50.17µ ± 2%   50.52µ ± 2%       ~ (p=0.588 n=20)
ParseError/long_prefix/validate   50.13µ ± 1%   50.11µ ± 1%       ~ (p=0.989 n=20)
geomean                           7.359µ        7.332µ       -0.37%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            19.86Mi ± 2%   20.02Mi ± 1%       ~ (p=0.973 n=20)
ParseE2E/select_where             36.18Mi ± 1%   36.13Mi ± 1%       ~ (p=0.867 n=20)
ParseE2E/join                     31.61Mi ± 3%   31.38Mi ± 4%       ~ (p=0.878 n=20)
ParseE2E/aggregate                28.15Mi ± 3%   28.81Mi ± 3%       ~ (p=0.163 n=20)
ParseE2E/subquery                 31.62Mi ± 1%   31.77Mi ± 1%       ~ (p=0.245 n=20)
ParseE2E/cte                      35.03Mi ± 1%   35.15Mi ± 2%       ~ (p=0.683 n=20)
ParseE2E/window                   39.46Mi ± 1%   40.16Mi ± 2%       ~ (p=0.101 n=20)
ParseE2E/complex                  36.83Mi ± 3%   36.98Mi ± 1%       ~ (p=0.542 n=20)
ParseE2E/insert                   39.36Mi ± 1%   39.45Mi ± 1%       ~ (p=0.569 n=20)
ParseE2E/update                   31.83Mi ± 1%   31.96Mi ± 1%       ~ (p=0.203 n=20)
ParseE2E/delete                   50.64Mi ± 4%   50.56Mi ± 5%       ~ (p=0.779 n=20)
ParseRW/postgresql/medium         31.35Mi ± 0%   31.42Mi ± 0%       ~ (p=0.304 n=20)
ParseRW/mysql/medium              39.42Mi ± 1%   39.65Mi ± 1%       ~ (p=0.087 n=20)
ParseRW/mariadb/medium            36.95Mi ± 2%   37.47Mi ± 4%       ~ (p=0.224 n=20)
ParseRW/sqlserver/medium          34.28Mi ± 1%   34.30Mi ± 0%       ~ (p=0.380 n=20)
ParseRW/oracle/medium             35.13Mi ± 1%   35.16Mi ± 1%       ~ (p=0.984 n=20)
ParseRW/sqlite/medium             37.53Mi ± 2%   37.56Mi ± 1%       ~ (p=0.763 n=20)
ParseRW/snowflake/medium          38.04Mi ± 2%   38.67Mi ± 5%       ~ (p=0.071 n=20)
ParseRW/clickhouse/medium         34.40Mi ± 1%   34.56Mi ± 1%       ~ (p=0.358 n=20)
ParseError/syntax_mid/parse       23.86Mi ± 1%   23.94Mi ± 1%       ~ (p=0.234 n=20)
ParseError/syntax_mid/validate    26.81Mi ± 2%   26.99Mi ± 1%       ~ (p=0.317 n=20)
ParseError/syntax_end/parse       17.81Mi ± 2%   17.84Mi ± 1%       ~ (p=0.218 n=20)
ParseError/syntax_end/validate    18.13Mi ± 3%   18.15Mi ± 2%       ~ (p=0.542 n=20)
ParseError/tokenize/parse         62.62Mi ± 2%   62.09Mi ± 2%       ~ (p=0.414 n=20)
ParseError/tokenize/validate      20.87Mi ± 1%   20.64Mi ± 2%       ~ (p=0.104 n=20)
ParseError/long_prefix/parse      46.04Mi ± 2%   45.71Mi ± 2%       ~ (p=0.588 n=20)
ParseError/long_prefix/validate   46.07Mi ± 1%   46.10Mi ± 1%       ~ (p=0.984 n=20)
geomean                           33.01Mi        33.12Mi       +0.33%

                                │      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.024Ki ± 0%     5.024Ki ± 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.89Ki ± 0%     65.89Ki ± 0%       ~ (p=0.771 n=20)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=1.000 n=20)
ParseRW/mariadb/medium            60.59Ki ± 0%     60.59Ki ± 0%  -0.00% (p=0.005 n=20)
ParseRW/sqlserver/medium          83.67Ki ± 0%     83.67Ki ± 0%       ~ (p=0.997 n=20)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=0.618 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) ¹
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.73Ki ± 0%     22.74Ki ± 0%       ~ (p=0.673 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   9.537µ ± 0%   9.517µ ± 0%       ~ (p=0.304 n=20)
ParseRW/mysql/medium        8.584µ ± 1%   8.533µ ± 1%       ~ (p=0.092 n=20)
ParseRW/mariadb/medium      10.66µ ± 2%   10.51µ ± 3%       ~ (p=0.224 n=20)
ParseRW/sqlserver/medium    9.241µ ± 1%   9.239µ ± 0%       ~ (p=0.380 n=20)
ParseRW/oracle/medium       9.941µ ± 1%   9.935µ ± 1%       ~ (p=0.973 n=20)
ParseRW/sqlite/medium       9.173µ ± 2%   9.168µ ± 1%       ~ (p=0.779 n=20)
ParseRW/snowflake/medium    10.28µ ± 2%   10.11µ ± 4%       ~ (p=0.072 n=20)
ParseRW/clickhouse/medium   9.357µ ± 1%   9.315µ ± 1%       ~ (p=0.358 n=20)
geomean                     9.577µ        9.524µ       -0.56%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                   │     base     │                head                 │
                   │    sec/op    │    sec/op     vs base               │
LintParallel/rules   18.00m ±  1%   17.99m ±  1%       ~ (p=0.698 n=20)
LintParallel/lint    33.84m ±  1%   34.00m ±  2%       ~ (p=0.429 n=20)
LintRW/postgresql    4.102m ±  1%   4.105m ±  1%       ~ (p=0.718 n=20)
LintRW/mysql         4.481m ±  1%   4.492m ±  1%       ~ (p=0.862 n=20)
LintRW/mariadb       4.083m ±  2%   4.095m ±  4%       ~ (p=0.698 n=20)
LintRW/sqlserver     3.896m ±  1%   3.914m ±  2%       ~ (p=0.678 n=20)
LintRW/oracle        4.687m ±  1%   4.672m ±  2%       ~ (p=0.989 n=20)
LintRW/sqlite        3.831m ±  3%   3.866m ±  1%       ~ (p=0.602 n=20)
LintRW/snowflake     3.968m ±  2%   3.961m ±  1%       ~ (p=0.738 n=20)
LintRW/clickhouse    4.436m ±  1%   4.495m ±  2%       ~ (p=0.265 n=20)
LintRule/L007        2.208m ±  1%   2.231m ±  1%       ~ (p=0.192 n=20)
LintRule/L010        634.2µ ± 20%   635.5µ ± 19%       ~ (p=0.602 n=20)
geomean              4.550m         4.567m        +0.37%

                  │     base     │                head                 │
                  │     B/s      │     B/s       vs base               │
LintRW/postgresql   6.900Mi ± 1%   6.900Mi ± 1%       ~ (p=0.723 n=20)
LintRW/mysql        7.305Mi ± 1%   7.286Mi ± 1%       ~ (p=0.909 n=20)
LintRW/mariadb      7.472Mi ± 2%   7.448Mi ± 4%       ~ (p=0.703 n=20)
LintRW/sqlserver    7.949Mi ± 1%   7.915Mi ± 2%       ~ (p=0.596 n=20)
LintRW/oracle       8.473Mi ± 1%   8.497Mi ± 2%       ~ (p=0.995 n=20)
LintRW/sqlite       7.496Mi ± 3%   7.424Mi ± 1%       ~ (p=0.587 n=20)
LintRW/snowflake    7.858Mi ± 2%   7.873Mi ± 1%       ~ (p=0.723 n=20)
LintRW/clickhouse   7.315Mi ± 1%   7.224Mi ± 2%       ~ (p=0.285 n=20)
geomean             7.583Mi        7.557Mi       -0.34%

                   │      base      │                 head                  │
                   │      B/op      │     B/op      vs base                 │
LintParallel/rules   5.559Mi ± 0%     5.559Mi ± 0%       ~ (p=0.925 n=20)
LintParallel/lint    25.53Mi ± 0%     25.53Mi ± 0%       ~ (p=0.551 n=20)
LintRW/postgresql    2.801Mi ± 0%     2.801Mi ± 0%       ~ (p=0.379 n=20)
LintRW/mysql         3.537Mi ± 0%     3.537Mi ± 0%       ~ (p=0.569 n=20)
LintRW/mariadb       3.252Mi ± 0%     3.252Mi ± 0%       ~ (p=0.163 n=20)
LintRW/sqlserver     2.838Mi ± 0%     2.838Mi ± 0%       ~ (p=0.064 n=20)
LintRW/oracle        3.477Mi ± 0%     3.477Mi ± 0%       ~ (p=0.066 n=20)
LintRW/sqlite        3.066Mi ± 0%     3.066Mi ± 0%       ~ (p=0.597 n=20)
LintRW/snowflake     3.083Mi ± 0%     3.083Mi ± 0%       ~ (p=0.794 n=20)
LintRW/clickhouse    3.474Mi ± 0%     3.474Mi ± 0%       ~ (p=0.663 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.859 n=20)
LintParallel/lint    147.5k ± 0%     147.5k ± 0%       ~ (p=0.723 n=20)
LintRW/postgresql    16.34k ± 0%     16.34k ± 0%       ~ (p=1.000 n=20)
LintRW/mysql         20.35k ± 0%     20.35k ± 0%       ~ (p=1.000 n=20)
LintRW/mariadb       18.48k ± 0%     18.48k ± 0%       ~ (p=0.341 n=20)
LintRW/sqlserver     15.94k ± 0%     15.94k ± 0%       ~ (p=0.480 n=20)
LintRW/oracle        19.67k ± 0%     19.67k ± 0%       ~ (p=1.000 n=20)
LintRW/sqlite        18.30k ± 0%     18.30k ± 0%       ~ (p=1.000 n=20)
LintRW/snowflake     17.77k ± 0%     17.77k ± 0%       ~ (p=1.000 n=20) ¹
LintRW/clickhouse    20.64k ± 0%     20.64k ± 0%       ~ (p=1.000 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   13.45µ ±  1%   13.46µ ±  1%       ~ (p=0.703 n=20)
LintRW/mysql        10.82µ ±  1%   10.85µ ±  1%       ~ (p=0.857 n=20)
LintRW/mariadb      10.74µ ±  2%   10.78µ ±  4%       ~ (p=0.703 n=20)
LintRW/sqlserver    14.07µ ±  1%   14.13µ ±  2%       ~ (p=0.654 n=20)
LintRW/oracle       12.74µ ±  1%   12.70µ ±  2%       ~ (p=0.989 n=20)
LintRW/sqlite       11.01µ ±  3%   11.11µ ±  1%       ~ (p=0.588 n=20)
LintRW/snowflake    12.13µ ±  2%   12.11µ ±  1%       ~ (p=0.733 n=20)
LintRW/clickhouse   11.26µ ±  1%   11.41µ ±  2%       ~ (p=0.262 n=20)
LintRule/L007       784.7n ±  1%   793.0n ±  1%       ~ (p=0.189 n=20)
LintRule/L010       225.4n ± 20%   225.9n ± 19%       ~ (p=0.616 n=20)
geomean             6.127µ         6.152µ        +0.41%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.081µ ± 2%   1.080µ ± 1%       ~ (p=0.743 n=20)
Tokenize/scratch/complex   2.873µ ± 1%   2.849µ ± 1%       ~ (p=0.402 n=20)
Tokenize/scratch/insert    545.5n ± 4%   541.6n ± 3%       ~ (p=0.698 n=20)
geomean                    1.192µ        1.185µ       -0.55%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      111.2Mi ± 2%   111.3Mi ± 1%       ~ (p=0.758 n=20)
Tokenize/scratch/complex   125.5Mi ± 1%   126.5Mi ± 1%       ~ (p=0.398 n=20)
Tokenize/scratch/insert    132.9Mi ± 4%   133.8Mi ± 3%       ~ (p=0.698 n=20)
geomean                    122.9Mi        123.5Mi       +0.54%

                         │     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.208µ ± 2%   1.212µ ± 2%       ~ (p=0.836 n=20)
TransformParse/join            3.979µ ± 2%   3.981µ ± 2%       ~ (p=0.743 n=20)
TransformParse/complex         11.16µ ± 1%   11.10µ ± 1%       ~ (p=0.383 n=20)
geomean                        3.772µ        3.769µ       -0.08%

                             │     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

This branch was successfully deployed

1 active deployment
Preview — bf222af0 Deployed Nov 4, 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