Skip to content

perf(linter): single-pass byte scan for L010 redundant whitespace [W6-04] - #902

Merged
ajitpratap0 merged 7 commits into
mainfrom
perf/w6-04-l010-byte-scan
Nov 3, 2026
Merged

ajitpratap0 merged 7 commits into
mainfrom
perf/w6-04-l010-byte-scan

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Wave-1 task W6-04 from the 2026-11 performance audit (LINT-3). The prototype was confirmed by an independent verifier. Codex ported it; Opus reviewed it and ran the full gate.

Before: for every line, L010 rebuilt each non-quoted part of the line with a strings.Builder and ran the regexp + over each part.

After: it scans the line's bytes once, tracking single and double quotes, and reports each run of two or more spaces that doesn't start the line. A run at the start of the line is indentation and is skipped, as before.

Edge cases: checked against the old code for indentation after tabs, runs after a closing quote, and unterminated strings. All behave the same.

One intended behaviour change, recorded in CHANGELOG: on lines with invalid UTF-8, columns are now true byte positions. The old rune loop wrote U+FFFD, which is 3 bytes, in place of each invalid byte, so columns drifted.

Merged with current main (wave 0 is in). The W0-02 goldens pass unchanged (TestGoldenLint and the full task check); no golden cell changed. The package-local benchmark was dropped in favour of the wave-0 matrix benchmark BenchmarkLintRule/L010 in pkg/linter.

Test Plan

Tests added:

  • Differential test against the previous implementation, kept in a test file as the oracle. Rule, line, column, message and suggestion must match. Inputs: 129 corpus files, 3,002 statements from testdata/realworld/* and testdata/corpus/*, and 300,000 seeded random lines. Under -race it runs a 30,000-line subset, which takes 2.3 s.
  • Invalid-UTF-8 columns get explicit tests for the new byte columns.

Gate, run outside the Codex sandbox:

Check Result
go run honnef.co/go/tools/cmd/staticcheck@latest ./pkg/linter/... ✅
go test -race -count=1 ./pkg/linter/... ✅
task check ✅
task test:cbinding ✅
go test -count=1 ./pkg/compatibility/... ✅

Local A/B (wave-0 matrix, indicative only; shared M4): task bench:ab BASE=origin/main 'BENCH=./pkg/linter=^BenchmarkLintRule$/^L010$', 10 rounds, benchstat.

Metric base head delta
sec/op 1494.8us 483.5us -67.66% (p=0.000)
B/op 828.9Ki 0 -100%
allocs/op 20.91k 0 -100%
  • Target: the plan's target was -50% or better; met locally.
  • Caveat: the corpora contain no L010 violations, so this measures clean input. The violation path appends a Violation exactly as before.

CI amd64 perf gate (shadow), run 41326073387: PASS. No gated metric moved past its threshold: gated-suite geomean +0.22% (within noise; A/A control 0 of 26 moved >3%), allocs/op unchanged and B/op +0.00% on every gated benchmark, instructions/op within +-0.5%. Note: the default gated suite (ParseE2E, ParseRW, ParseFresh, PoolGetPut, Tokenize) does not include BenchmarkLintRule/L010, so the gate confirms no collateral regression but does not itself measure this task's target. The target gain is the local M4 number above (-67.7%, 0 allocs); an amd64 measurement of LintRule/L010 was not available from the gate.

Breaking Changes

None to the API. The one behaviour change is the invalid-UTF-8 column numbers described in the summary.

Update after review (merge of origin/main 9d3d8b0)

  • Merged origin/main (W3-01 perf(parser): type-gated preprocess fast path [W3-01] #919); CHANGELOG conflict resolved into the single ### Performance section.
  • CHANGELOG figure aligned to the measured number: ~68% on BenchmarkLintRule/L010 (local M4), allocs 20.9k to 0. Independent re-run: 520.0n to 154.4n sec/stmt (n=4).
  • amd64 gate (default suite) shows no collateral regression; it has no LintRule/L010 rows, so the target is not yet verified on amd64. Pending orchestrator decision (extend gate or waive).
  • AC-STD re-run after merge: task check, test:cbinding, compatibility, staticcheck all pass.
  • Follow-up commit: dropped the equivalent-mutant !inString guard (commented) and added named edge-case column tests (tab then spaces, run after closing quote, unterminated quote, whitespace-only line).
  • amd64 status: the gate suite (scripts/perf/gate-suite.txt) has no ./pkg/linter line, so BenchmarkLintRule/L010 cannot be measured on amd64 CI. Merge needs either a suite-extension PR (add ./pkg/linter ^BenchmarkLintRule$/^L010$ and ^BenchmarkLintRW$) or an explicit orchestrator waiver; neither has been given yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

…-04]

L010 rebuilt every non-quoted part of a line with a strings.Builder and ran
the regexp `  +` over each part. It now scans the line's bytes once,
tracking single and double quotes, and reports each run of two or more
spaces that does not start the line (indentation). Violations are
identical except on invalid UTF-8, where columns are now true byte
positions (the old rune loop wrote U+FFFD, shifting columns).

Tests: a differential test against the previous implementation (kept in a
test file as the oracle) over 129 corpus files, 3,002 statements and
300,000 seeded random lines (30,000 under -race, 2.3 s); explicit tests for
the invalid-UTF-8 column change.

M4, GOGC=off -cpu 1, 10 interleaved rounds, LintRule/L010 per dialect:
-70.3% to -74.6% (geomean -71.4%), B/op and allocs/op 0 (from ~100 KiB and
~2.5k). The real-world corpora have no L010 violations, so this measures
clean input; the violation path appends a Violation as before.

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

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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

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

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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


Review of PR #902: single-pass byte scan for L010

Verdict: Looks good. I found no blocking issues. I read the diff but did not run the tests or benchmarks. This relies on your reported gate results.

Correctness

  • Indentation: runStart != 0 matches the old "leading run at byte 0" skip. I traced "\t x" through both versions. The run starts at byte 1, so both report it.
  • Quotes: A quote ends the current run via report(i), so runs never span a string. '' escapes toggle the string state twice, exactly as the old code did.
  • Unterminated string: The trailing report(len(line)) is skipped when inString is true. Any run before the open quote was already flushed at the quote, so nothing is lost or double-counted.
  • Columns: runStart + 1 is a byte offset, as the CHANGELOG states. Only lines with invalid UTF-8 change. Valid UTF-8 gives the same bytes and the same column as before, because the old code also used byte indices from range.
  • Tabs: Only ' ' counts as a space, as before. A mix like " \t " does not form a run, which also matches the regex +.

Performance and design

  • Removing the regexp, the strings.Builder and the linePart slices explains the 0 allocs and the −71% result.
  • The report closure is created once per line. Your 0 allocs/op shows it doesn't escape. Moving it outside the loop would be cleaner but isn't needed.
  • Check that strings is still used elsewhere in the file (for example in Fix). It compiles per your gate, so this is fine.

Tests

  • Using the old implementation as a differential oracle is a good approach. It covers corpus files, realworld statements and 300k random lines.
  • The race and norace build-tag constants are a reasonable way to shrink the run under -race.
  • Two small gaps:
    • Add a few explicit table cases for the edge cases you checked by hand (tab then spaces, run right after a closing quote, unterminated quote). Then a future change that also alters the oracle can't hide a regression.
    • As you note, the corpora contain no L010 violations. That is fine for correctness because the random-line fuzz exercises the violation path, but the benchmark only measures clean input.

Security

No concerns. The scan is a bounded O(n) loop with no allocation driven by input.

Process

This is a draft until the W0-02 behaviour goldens and the W0-09 perf gate are on main. That ordering is sensible. Once the goldens land, re-run them against this branch, because the invalid-UTF-8 column change could show up there.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #902: single-pass byte scan for L010

Verdict: No blocking issues. I read the diff of redundant_whitespace.go and the CHANGELOG, and traced the edge cases by hand. I did not run the tests or benchmarks, so the gate results are as you reported them.

Correctness

  • Indentation: runStart != 0 matches the old skip. A whitespace-only line such as " " is skipped by both versions. "\t x" is reported at column 2 by both.
  • Quotes: report(i) at an opening quote flushes the pending run, so runs never span a string. '' escapes toggle the state twice, as the old code did.
  • After a closing quote: 'a' x reports column 4 in both versions.
  • Unterminated string: the final report(len(line)) is skipped when inString is true. Any earlier run was already flushed at the opening quote.
  • Invalid UTF-8: the byte-column change is intended and is recorded in the CHANGELOG. For valid UTF-8, columns are identical because the old range indices were already byte offsets.

Performance and design

  • Dropping the regexp, the strings.Builder and the linePart slices explains the 0 allocs/op and the −71% result.
  • Optional: define report once outside the line loop, with line and lineNum passed in or captured by reference. It would make the loop easier to read. The 0 allocs/op shows the closure isn't costing anything now.

Tests

  • The differential test against the old implementation is a strong check. It covers the corpus, the realworld statements and 300k random lines, with a smaller subset under -race.
  • Suggestion: add a few explicit table cases for the edge cases above (tab then spaces, run right after a closing quote, unterminated quote, whitespace-only line). If the oracle and the implementation ever change together, a regression could otherwise go unnoticed.
  • The benchmark corpora contain no L010 violations, so the −71% figure measures clean input. The random-line differential test does cover the violation path for correctness.

Security

No concerns. The scan is a bounded O(n) loop with no input-driven allocation.

Process

Draft status is sensible while W0-02 and W0-09 are pending. Re-run the behaviour goldens once they land, because the invalid-UTF-8 column change could show up there.
· Branch: perf/w6-04-l010-byte-scan

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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

Base f6186369f802, head 4ae43f51402d. 10 interleaved rounds, benchtime 200ms, -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.

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

Rules: time regresses at >= +3% with p < 0.01 (Mann-Whitney U), or a suite geomean >= +1.5% (per-round 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%. An A/A control with more than 10% of benchmarks moving over 3% is noisy: re-run once, then inconclusive (neutral).

Gated suite (GOGC=off, GOMAXPROCS=1)

benchmark time p A/A B/op allocs/op status
ParseE2E/simple_select +0.10% 0.791 -0.24% +0.00% 5 → 5 ok
ParseE2E/select_where -0.58% 0.064 -0.24% +0.00% 18 → 18 ok
ParseE2E/join -0.37% 0.739 -0.09% +0.00% 17 → 17 ok
ParseE2E/aggregate -0.74% 0.011 -0.18% +0.00% 18 → 18 ok
ParseE2E/subquery -0.03% 0.820 +0.37% +0.00% 14 → 14 ok
ParseE2E/cte -1.42% 0.247 -1.29% +0.00% 21 → 21 ok
ParseE2E/window -4.41% 0.104 -1.81% +0.00% 11 → 11 ok
ParseE2E/complex -1.42% 0.218 -1.77% +0.00% 49 → 49 ok
ParseE2E/insert -0.46% 0.257 -0.23% +0.00% 17 → 17 ok
ParseE2E/update -0.05% 0.677 -0.18% +0.00% 13 → 13 ok
ParseE2E/delete +0.45% 0.850 +0.37% +0.00% 5 → 5 ok
ParseRW/postgresql/medium -0.28% 0.579 +0.11% +0.00% 646 → 646 ok
ParseRW/mysql/medium +0.22% 0.912 +0.68% +0.00% 368 → 368 ok
ParseRW/mariadb/medium -0.90% 0.011 -0.11% -0.00% 528 → 528 ok
ParseRW/sqlserver/medium +4.06% 0.043 -0.69% +0.00% 927 → 927 ok
ParseRW/oracle/medium +0.94% 0.029 +0.60% +0.00% 1090 → 1090 ok
ParseRW/sqlite/medium +0.14% 0.796 +0.38% +0.00% 347 → 347 ok
ParseRW/snowflake/medium -0.36% 0.684 +0.15% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium -3.29% 0.089 -3.00% +0.00% 383 → 383 ok
ParseFresh/simple_select -0.01% 0.940 -0.08% +0.00% 6 → 6 ok
ParseFresh/join -0.37% 0.427 -0.29% +0.00% 18 → 18 ok
ParseFresh/complex +0.19% 0.315 +0.02% +0.00% 50 → 50 ok
PoolGetPut/serial +0.20% 0.064 -0.03% +0.00% 0 → 0 ok
Tokenize/scratch/join +0.65% <0.001 -0.03% +0.00% 0 → 0 ok
Tokenize/scratch/complex +0.53% 0.012 -0.18% +0.00% 0 → 0 ok
Tokenize/scratch/insert +0.56% <0.001 -0.41% +0.00% 0 → 0 ok
Normalize/simple_select +0.42% 0.003 +0.00% +0.00% 7 → 7 ok
Normalize/join +1.44% 0.005 -0.16% +0.00% 23 → 23 ok
Normalize/complex -0.09% 0.970 +0.06% +0.00% 68 → 68 ok
TransformParse/simple_select +1.77% 0.031 +0.94% +0.00% 5 → 5 ok
TransformParse/join -0.12% 0.570 +0.15% +0.00% 17 → 17 ok
TransformParse/complex -3.87% 0.353 +0.30% +0.00% 49 → 49 ok
LintRW/postgresql -6.09% 0.002 -0.12% -2.60% 36485 → 34188 faster, -allocs
LintRW/mysql -5.74% <0.001 -0.70% -2.36% 44207 → 41489 faster, -allocs
LintRW/mariadb -6.08% <0.001 +0.09% -2.38% 40741 → 38235 faster, -allocs
LintRW/sqlserver -6.58% <0.001 -0.67% -2.80% 36896 → 34651 faster, -allocs
LintRW/oracle -6.07% <0.001 +1.32% -3.17% 45291 → 42243 faster, -allocs
LintRW/sqlite -7.18% <0.001 +0.32% -2.99% 40583 → 37576 faster, -allocs
LintRW/snowflake -7.41% <0.001 +0.41% -2.53% 39844 → 37558 faster, -allocs
LintRW/clickhouse -7.07% <0.001 +0.35% -2.31% 44995 → 42343 faster, -allocs
LintRule/L007 -0.10% 0.853 -0.29% +0.00% 72565 → 72565 ok
LintRule/L010 -80.04% <0.001 +0.15% -100.00% 20758 → 0 faster, -allocs
ParseError/syntax_mid/parse -0.17% 0.288 -0.02% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate +0.78% 0.049 +0.11% +0.00% 12 → 12 ok
ParseError/syntax_end/parse -1.19% 0.241 -0.02% +0.00% 16 → 16 ok
ParseError/syntax_end/validate +0.14% 0.684 +0.28% +0.00% 16 → 16 ok
ParseError/tokenize/parse -0.44% 0.880 +1.90% +0.00% 4 → 4 ok
ParseError/tokenize/validate -0.15% 0.762 -0.17% +0.00% 21 → 21 ok
ParseError/long_prefix/parse +0.08% 0.734 +0.16% +0.00% 219 → 219 ok
ParseError/long_prefix/validate -0.41% 0.165 -0.44% +0.00% 219 → 219 ok
geomean -4.38% <0.001 -0.09%

A/A control: 0 of 50 benchmarks moved more than 3%.

Instructions per operation (cmd/perfdriver)

case instructions/op status
e2e/aggregate +0.01% ok
e2e/complex +0.03% ok
e2e/cte +0.04% ok
e2e/delete -0.11% ok
e2e/insert +0.00% ok
e2e/join +0.00% ok
e2e/select_where -0.08% ok
e2e/simple_select +0.02% ok
e2e/subquery +0.02% ok
e2e/update +0.05% ok
e2e/window +0.01% ok
rw/postgresql/medium +0.07% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select +0.03% 0.676
ParseE2E/select_where -0.34% 0.353
ParseE2E/join -0.48% 0.315
ParseE2E/aggregate -1.73% 0.002
ParseE2E/subquery -0.47% 0.037
ParseE2E/cte -0.05% 0.705
ParseE2E/window +0.01% 0.940
ParseE2E/complex -0.76% 0.705
ParseE2E/insert -0.11% 0.520
ParseE2E/update +0.37% 0.130
ParseE2E/delete +0.80% 0.112
ParseRW/postgresql/medium +0.09% 0.971
ParseRW/mysql/medium -0.15% 0.796
ParseRW/mariadb/medium -0.09% 0.579
ParseRW/sqlserver/medium +0.20% 0.579
ParseRW/oracle/medium +0.28% 0.631
ParseRW/sqlite/medium +0.05% 0.684
ParseRW/snowflake/medium +0.69% 0.247
ParseRW/clickhouse/medium -0.25% 0.436
ParseFresh/simple_select +0.36% 0.130
ParseFresh/join +0.68% 0.007
ParseFresh/complex +0.02% 0.496
PoolGetPut/serial +0.07% 0.324
Tokenize/scratch/join +0.37% 0.383
Tokenize/scratch/complex +0.23% 0.070
Tokenize/scratch/insert +0.65% 0.041
Normalize/simple_select +0.12% 0.272
Normalize/join +0.52% 0.143
Normalize/complex +0.16% 0.880
TransformParse/simple_select -0.15% 0.519
TransformParse/join -0.09% 0.545
TransformParse/complex -0.44% 0.165
LintRW/postgresql -6.61% <0.001
LintRW/mysql -6.10% <0.001
LintRW/mariadb -5.79% <0.001
LintRW/sqlserver -6.73% <0.001
LintRW/oracle -7.19% <0.001
LintRW/sqlite -7.43% <0.001
LintRW/snowflake -6.88% <0.001
LintRW/clickhouse -6.22% <0.001
LintRule/L007 -0.82% 0.315
LintRule/L010 -82.55% <0.001
ParseError/syntax_mid/parse +0.19% 0.820
ParseError/syntax_mid/validate -0.57% 0.364
ParseError/syntax_end/parse -0.64% 0.121
ParseError/syntax_end/validate -0.58% 0.121
ParseError/tokenize/parse -0.57% 0.015
ParseError/tokenize/validate +0.06% 0.520
ParseError/long_prefix/parse -0.78% 0.218
ParseError/long_prefix/validate -0.10% 0.481
geomean -4.55%
Environment
date:        2026-11-03T15:03:29Z
base:        f6186369f802aa56a7119ab7b47e70844da0b5e6
head:        working tree of 4ae43f51402d2aded9933148ab0322618a4c298c
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
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        1.72, 0.44, 0.15
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)
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.153µ ± 0%   2.162µ ± 1%  +0.42% (p=0.002 n=10)
Normalize/join            7.170µ ± 1%   7.273µ ± 1%  +1.44% (p=0.005 n=10)
Normalize/complex         18.25µ ± 1%   18.24µ ± 1%       ~ (p=0.953 n=10)
geomean                   6.555µ        6.594µ       +0.59%

                        │     base     │                 head                  │
                        │     B/op     │     B/op      vs base                 │
Normalize/simple_select     896.0 ± 0%     896.0 ± 0%       ~ (p=1.000 n=10) ¹
Normalize/join            2.211Ki ± 0%   2.211Ki ± 0%       ~ (p=1.000 n=10) ¹
Normalize/complex         6.117Ki ± 0%   6.117Ki ± 0%       ~ (p=1.000 n=10) ¹
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=10) ¹
Normalize/join            23.00 ± 0%   23.00 ± 0%       ~ (p=1.000 n=10) ¹
Normalize/complex         68.00 ± 0%   68.00 ± 0%       ~ (p=1.000 n=10) ¹
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.436µ ± 1%   1.438µ ± 3%       ~ (p=0.782 n=10)
ParseE2E/select_where             3.361µ ± 1%   3.341µ ± 2%       ~ (p=0.063 n=10)
ParseE2E/join                     4.839µ ± 4%   4.821µ ± 4%       ~ (p=0.739 n=10)
ParseE2E/aggregate                3.521µ ± 1%   3.495µ ± 1%  -0.74% (p=0.008 n=10)
ParseE2E/subquery                 3.091µ ± 4%   3.090µ ± 2%       ~ (p=0.809 n=10)
ParseE2E/cte                      4.337µ ± 2%   4.276µ ± 3%       ~ (p=0.247 n=10)
ParseE2E/window                   2.764µ ± 5%   2.642µ ± 5%       ~ (p=0.102 n=10)
ParseE2E/complex                  12.53µ ± 3%   12.35µ ± 2%       ~ (p=0.218 n=10)
ParseE2E/insert                   2.178µ ± 1%   2.167µ ± 1%       ~ (p=0.256 n=10)
ParseE2E/update                   1.958µ ± 1%   1.957µ ± 2%       ~ (p=0.670 n=10)
ParseE2E/delete                   1.229µ ± 6%   1.235µ ± 1%       ~ (p=0.838 n=10)
ParseRW/postgresql/medium         152.8µ ± 1%   152.4µ ± 1%       ~ (p=0.579 n=10)
ParseRW/mysql/medium              92.57µ ± 1%   92.78µ ± 1%       ~ (p=0.912 n=10)
ParseRW/mariadb/medium            134.3µ ± 1%   133.1µ ± 1%       ~ (p=0.011 n=10)
ParseRW/sqlserver/medium          273.7µ ± 1%   284.8µ ± 4%       ~ (p=0.043 n=10)
ParseRW/oracle/medium             327.7µ ± 1%   330.8µ ± 1%       ~ (p=0.029 n=10)
ParseRW/sqlite/medium             72.95µ ± 2%   73.04µ ± 1%       ~ (p=0.796 n=10)
ParseRW/snowflake/medium          189.4µ ± 1%   188.7µ ± 1%       ~ (p=0.684 n=10)
ParseRW/clickhouse/medium         90.66µ ± 3%   87.68µ ± 5%       ~ (p=0.089 n=10)
ParseFresh/simple_select          6.986µ ± 0%   6.985µ ± 1%       ~ (p=0.928 n=10)
ParseFresh/join                   10.81µ ± 1%   10.77µ ± 0%       ~ (p=0.425 n=10)
ParseFresh/complex                18.35µ ± 1%   18.38µ ± 2%       ~ (p=0.315 n=10)
PoolGetPut/serial                 66.39n ± 3%   66.52n ± 1%       ~ (p=0.060 n=10)
ParseError/syntax_mid/parse       2.390µ ± 2%   2.386µ ± 1%       ~ (p=0.287 n=10)
ParseError/syntax_mid/validate    2.185µ ± 1%   2.202µ ± 1%       ~ (p=0.045 n=10)
ParseError/syntax_end/parse       2.554µ ± 2%   2.524µ ± 2%       ~ (p=0.239 n=10)
ParseError/syntax_end/validate    2.530µ ± 2%   2.534µ ± 5%       ~ (p=0.684 n=10)
ParseError/tokenize/parse         923.7n ± 3%   919.6n ± 4%       ~ (p=0.867 n=10)
ParseError/tokenize/validate      2.646µ ± 2%   2.642µ ± 1%       ~ (p=0.753 n=10)
ParseError/long_prefix/parse      58.53µ ± 2%   58.58µ ± 1%       ~ (p=0.725 n=10)
ParseError/long_prefix/validate   58.81µ ± 1%   58.57µ ± 1%       ~ (p=0.165 n=10)
geomean                           9.316µ        9.286µ       -0.32%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            17.27Mi ± 1%   17.26Mi ± 3%       ~ (p=0.810 n=10)
ParseE2E/select_where             29.23Mi ± 1%   29.41Mi ± 2%       ~ (p=0.063 n=10)
ParseE2E/join                     24.83Mi ± 4%   24.93Mi ± 4%       ~ (p=0.739 n=10)
ParseE2E/aggregate                22.76Mi ± 1%   22.93Mi ± 1%  +0.73% (p=0.008 n=10)
ParseE2E/subquery                 25.30Mi ± 4%   25.31Mi ± 2%       ~ (p=0.812 n=10)
ParseE2E/cte                      27.26Mi ± 2%   27.66Mi ± 3%       ~ (p=0.247 n=10)
ParseE2E/window                   32.43Mi ± 5%   33.94Mi ± 5%       ~ (p=0.105 n=10)
ParseE2E/complex                  28.78Mi ± 3%   29.20Mi ± 2%       ~ (p=0.218 n=10)
ParseE2E/insert                   33.29Mi ± 1%   33.44Mi ± 1%       ~ (p=0.240 n=10)
ParseE2E/update                   26.80Mi ± 1%   26.81Mi ± 2%       ~ (p=0.698 n=10)
ParseE2E/delete                   40.35Mi ± 6%   40.18Mi ± 1%       ~ (p=0.853 n=10)
ParseRW/postgresql/medium         23.47Mi ± 1%   23.54Mi ± 1%       ~ (p=0.564 n=10)
ParseRW/mysql/medium              29.23Mi ± 1%   29.17Mi ± 1%       ~ (p=0.868 n=10)
ParseRW/mariadb/medium            26.41Mi ± 1%   26.65Mi ± 1%       ~ (p=0.011 n=10)
ParseRW/sqlserver/medium          24.31Mi ± 1%   23.37Mi ± 5%       ~ (p=0.043 n=10)
ParseRW/oracle/medium             25.58Mi ± 1%   25.34Mi ± 1%       ~ (p=0.027 n=10)
ParseRW/sqlite/medium             28.31Mi ± 2%   28.28Mi ± 1%       ~ (p=0.796 n=10)
ParseRW/snowflake/medium          26.85Mi ± 1%   26.95Mi ± 1%       ~ (p=0.698 n=10)
ParseRW/clickhouse/medium         24.86Mi ± 3%   25.71Mi ± 5%       ~ (p=0.089 n=10)
ParseError/syntax_mid/parse       19.15Mi ± 2%   19.19Mi ± 1%       ~ (p=0.254 n=10)
ParseError/syntax_mid/validate    20.95Mi ± 1%   20.79Mi ± 1%       ~ (p=0.041 n=10)
ParseError/syntax_end/parse       14.56Mi ± 2%   14.74Mi ± 2%       ~ (p=0.247 n=10)
ParseError/syntax_end/validate    14.71Mi ± 2%   14.68Mi ± 5%       ~ (p=0.699 n=10)
ParseError/tokenize/parse         47.49Mi ± 3%   47.70Mi ± 3%       ~ (p=0.853 n=10)
ParseError/tokenize/validate      16.58Mi ± 2%   16.61Mi ± 1%       ~ (p=0.739 n=10)
ParseError/long_prefix/parse      39.47Mi ± 2%   39.43Mi ± 1%       ~ (p=0.796 n=10)
ParseError/long_prefix/validate   39.28Mi ± 1%   39.44Mi ± 1%       ~ (p=0.165 n=10)
geomean                           25.92Mi        26.02Mi       +0.38%

                                │      base      │                 head                  │
                                │      B/op      │     B/op      vs base                 │
ParseE2E/simple_select              816.0 ± 0%       816.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/select_where             1.523Ki ± 0%     1.523Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/join                     1.836Ki ± 0%     1.836Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/aggregate                1.812Ki ± 0%     1.812Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/subquery                 1.828Ki ± 0%     1.828Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/cte                      2.336Ki ± 0%     2.336Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/window                   1.281Ki ± 0%     1.281Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/complex                  5.024Ki ± 0%     5.024Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/insert                     824.0 ± 0%       824.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/update                     736.0 ± 0%       736.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/delete                     400.0 ± 0%       400.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/postgresql/medium         65.87Ki ± 0%     65.87Ki ± 0%       ~ (p=1.000 n=10)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/mariadb/medium            60.57Ki ± 0%     60.57Ki ± 0%       ~ (p=0.365 n=10)
ParseRW/sqlserver/medium          83.66Ki ± 0%     83.66Ki ± 0%       ~ (p=1.000 n=10)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=1.000 n=10)
ParseRW/sqlite/medium             38.12Ki ± 0%     38.12Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/snowflake/medium          77.63Ki ± 0%     77.63Ki ± 0%       ~ (p=0.211 n=10)
ParseRW/clickhouse/medium         34.82Ki ± 0%     34.82Ki ± 0%       ~ (p=1.000 n=10)
ParseFresh/simple_select            896.0 ± 0%       896.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/join                   1.914Ki ± 0%     1.914Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/complex                5.102Ki ± 0%     5.102Ki ± 0%       ~ (p=1.000 n=10) ¹
PoolGetPut/serial                   0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_mid/parse         680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_mid/validate      680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_end/parse       1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_end/validate    1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseError/tokenize/parse           272.0 ± 0%       272.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/tokenize/validate      1.758Ki ± 0%     1.758Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseError/long_prefix/parse      22.71Ki ± 0%     22.71Ki ± 0%       ~ (p=0.582 n=10)
ParseError/long_prefix/validate   22.68Ki ± 0%     22.68Ki ± 0%       ~ (p=1.000 n=10) ¹
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=10) ¹
ParseE2E/select_where              18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/join                      17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/aggregate                 18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/subquery                  14.00 ± 0%      14.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/cte                       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/window                    11.00 ± 0%      11.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/complex                   49.00 ± 0%      49.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/insert                    17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/update                    13.00 ± 0%      13.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseE2E/delete                    5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/postgresql/medium          646.0 ± 0%      646.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/mysql/medium               368.0 ± 0%      368.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/mariadb/medium             528.0 ± 0%      528.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/sqlserver/medium           927.0 ± 0%      927.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/oracle/medium             1.090k ± 0%     1.090k ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/sqlite/medium              347.0 ± 0%      347.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/snowflake/medium           776.0 ± 0%      776.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/clickhouse/medium          383.0 ± 0%      383.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/simple_select           6.000 ± 0%      6.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/join                    18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/complex                 50.00 ± 0%      50.00 ± 0%       ~ (p=1.000 n=10) ¹
PoolGetPut/serial                  0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_mid/parse        12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_mid/validate     12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_end/parse        16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/syntax_end/validate     16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/tokenize/parse          4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/tokenize/validate       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/long_prefix/parse       219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseError/long_prefix/validate    219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=10) ¹
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   12.74µ ± 1%   12.70µ ± 1%       ~ (p=0.592 n=10)
ParseRW/mysql/medium        11.57µ ± 1%   11.60µ ± 1%       ~ (p=0.869 n=10)
ParseRW/mariadb/medium      14.92µ ± 1%   14.78µ ± 1%       ~ (p=0.011 n=10)
ParseRW/sqlserver/medium    13.03µ ± 1%   13.56µ ± 4%       ~ (p=0.043 n=10)
ParseRW/oracle/medium       13.65µ ± 1%   13.78µ ± 1%       ~ (p=0.027 n=10)
ParseRW/sqlite/medium       12.16µ ± 2%   12.17µ ± 1%       ~ (p=0.796 n=10)
ParseRW/snowflake/medium    14.57µ ± 1%   14.51µ ± 1%       ~ (p=0.684 n=10)
ParseRW/clickhouse/medium   12.95µ ± 3%   12.53µ ± 5%       ~ (p=0.089 n=10)
geomean                     13.16µ        13.16µ       +0.05%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                  │     base     │                head                 │
                  │    sec/op    │   sec/op     vs base                │
LintRW/postgresql    6.683m ± 1%   6.276m ± 3%   -6.09% (p=0.002 n=10)
LintRW/mysql         7.520m ± 2%   7.088m ± 1%   -5.74% (p=0.000 n=10)
LintRW/mariadb       6.938m ± 2%   6.516m ± 1%   -6.08% (p=0.000 n=10)
LintRW/sqlserver     6.697m ± 6%   6.257m ± 1%   -6.58% (p=0.000 n=10)
LintRW/oracle        7.889m ± 3%   7.410m ± 1%   -6.07% (p=0.000 n=10)
LintRW/sqlite        6.664m ± 1%   6.186m ± 2%   -7.18% (p=0.000 n=10)
LintRW/snowflake     6.782m ± 2%   6.279m ± 3%   -7.41% (p=0.000 n=10)
LintRW/clickhouse    7.545m ± 2%   7.012m ± 2%   -7.07% (p=0.000 n=10)
LintRule/L007        8.261m ± 2%   8.253m ± 2%        ~ (p=0.853 n=10)
LintRule/L010       3516.3µ ± 2%   702.0µ ± 1%  -80.04% (p=0.000 n=10)
geomean              6.701m        5.403m       -19.36%

                  │     base     │                head                 │
                  │     B/s      │     B/s       vs base               │
LintRW/postgresql   4.234Mi ± 1%   4.511Mi ± 3%  +6.53% (p=0.001 n=10)
LintRW/mysql        4.354Mi ± 2%   4.616Mi ± 1%  +6.02% (p=0.000 n=10)
LintRW/mariadb      4.396Mi ± 2%   4.678Mi ± 1%  +6.40% (p=0.000 n=10)
LintRW/sqlserver    4.625Mi ± 6%   4.950Mi ± 2%  +7.01% (p=0.001 n=10)
LintRW/oracle       5.035Mi ± 3%   5.360Mi ± 1%  +6.44% (p=0.000 n=10)
LintRW/sqlite       4.306Mi ± 1%   4.640Mi ± 2%  +7.75% (p=0.000 n=10)
LintRW/snowflake    4.597Mi ± 2%   4.969Mi ± 3%  +8.09% (p=0.000 n=10)
LintRW/clickhouse   4.306Mi ± 2%   4.630Mi ± 2%  +7.53% (p=0.000 n=10)
geomean             4.475Mi        4.787Mi       +6.97%

                  │     base     │                   head                    │
                  │     B/op     │     B/op      vs base                     │
LintRW/postgresql   3.498Mi ± 0%   3.407Mi ± 0%    -2.60% (p=0.000 n=10)
LintRW/mysql        4.352Mi ± 0%   4.250Mi ± 0%    -2.36% (p=0.000 n=10)
LintRW/mariadb      4.026Mi ± 0%   3.930Mi ± 0%    -2.38% (p=0.000 n=10)
LintRW/sqlserver    3.594Mi ± 0%   3.493Mi ± 0%    -2.80% (p=0.000 n=10)
LintRW/oracle       4.387Mi ± 0%   4.247Mi ± 0%    -3.17% (p=0.000 n=10)
LintRW/sqlite       3.816Mi ± 0%   3.702Mi ± 0%    -2.99% (p=0.000 n=10)
LintRW/snowflake    3.847Mi ± 0%   3.750Mi ± 0%    -2.53% (p=0.000 n=10)
LintRW/clickhouse   4.296Mi ± 0%   4.196Mi ± 0%    -2.31% (p=0.000 n=10)
LintRule/L007       2.942Mi ± 0%   2.942Mi ± 0%         ~ (p=1.000 n=10)
LintRule/L010       859.6Ki ± 0%     0.0Ki ± 0%  -100.00% (p=0.000 n=10)
geomean             3.294Mi                      ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean

                  │    base     │                   head                   │
                  │  allocs/op  │  allocs/op   vs base                     │
LintRW/postgresql   36.48k ± 0%   34.19k ± 0%    -6.30% (p=0.000 n=10)
LintRW/mysql        44.21k ± 0%   41.49k ± 0%    -6.15% (p=0.000 n=10)
LintRW/mariadb      40.74k ± 0%   38.23k ± 0%    -6.15% (p=0.000 n=10)
LintRW/sqlserver    36.90k ± 0%   34.65k ± 0%    -6.08% (p=0.000 n=10)
LintRW/oracle       45.29k ± 0%   42.24k ± 0%    -6.73% (p=0.000 n=10)
LintRW/sqlite       40.58k ± 0%   37.58k ± 0%    -7.41% (p=0.000 n=10)
LintRW/snowflake    39.84k ± 0%   37.56k ± 0%    -5.74% (p=0.000 n=10)
LintRW/clickhouse   44.99k ± 0%   42.34k ± 0%    -5.89% (p=0.000 n=10)
LintRule/L007       72.56k ± 0%   72.56k ± 0%         ~ (p=1.000 n=10) ¹
LintRule/L010       20.76k ± 0%    0.00k ± 0%  -100.00% (p=0.000 n=10)
geomean             40.55k                     ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                  │     base     │                head                 │
                  │   sec/stmt   │  sec/stmt    vs base                │
LintRW/postgresql    21.91µ ± 1%   20.58µ ± 3%   -6.09% (p=0.002 n=10)
LintRW/mysql         18.16µ ± 2%   17.12µ ± 1%   -5.74% (p=0.000 n=10)
LintRW/mariadb       18.26µ ± 2%   17.15µ ± 1%   -6.08% (p=0.000 n=10)
LintRW/sqlserver     24.18µ ± 6%   22.59µ ± 1%   -6.57% (p=0.000 n=10)
LintRW/oracle        21.44µ ± 3%   20.14µ ± 1%   -6.07% (p=0.000 n=10)
LintRW/sqlite        19.15µ ± 1%   17.78µ ± 2%   -7.18% (p=0.000 n=10)
LintRW/snowflake     20.74µ ± 2%   19.20µ ± 3%   -7.41% (p=0.000 n=10)
LintRW/clickhouse    19.15µ ± 2%   17.80µ ± 2%   -7.07% (p=0.000 n=10)
LintRule/L007        2.937µ ± 2%   2.934µ ± 2%        ~ (p=0.868 n=10)
LintRule/L010       1250.0n ± 2%   249.6n ± 1%  -80.03% (p=0.000 n=10)
geomean              12.65µ        10.20µ       -19.36%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.472µ ± 0%   1.482µ ± 1%  +0.65% (p=0.000 n=10)
Tokenize/scratch/complex   3.930µ ± 0%   3.951µ ± 1%  +0.53% (p=0.009 n=10)
Tokenize/scratch/insert    736.1n ± 0%   740.2n ± 0%  +0.56% (p=0.000 n=10)
geomean                    1.621µ        1.630µ       +0.58%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      81.62Mi ± 0%   81.10Mi ± 1%  -0.64% (p=0.000 n=10)
Tokenize/scratch/complex   91.72Mi ± 0%   91.24Mi ± 1%  -0.53% (p=0.009 n=10)
Tokenize/scratch/insert    98.46Mi ± 0%   97.92Mi ± 0%  -0.55% (p=0.000 n=10)
geomean                    90.33Mi        89.81Mi       -0.57%

                         │     base     │                head                 │
                         │     B/op     │    B/op     vs base                 │
Tokenize/scratch/join      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
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=10) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=10) ¹
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.386µ ± 2%   1.410µ ± 2%       ~ (p=0.027 n=10)
TransformParse/join            4.801µ ± 4%   4.796µ ± 3%       ~ (p=0.566 n=10)
TransformParse/complex         12.66µ ± 4%   12.17µ ± 6%       ~ (p=0.353 n=10)
geomean                        4.383µ        4.350µ       -0.77%

                             │     base     │                 head                  │
                             │     B/op     │     B/op      vs base                 │
TransformParse/simple_select     816.0 ± 0%     816.0 ± 0%       ~ (p=1.000 n=10) ¹
TransformParse/join            1.836Ki ± 0%   1.836Ki ± 0%       ~ (p=1.000 n=10) ¹
TransformParse/complex         5.023Ki ± 0%   5.023Ki ± 0%       ~ (p=1.000 n=10) ¹
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=10) ¹
TransformParse/join            17.00 ± 0%   17.00 ± 0%       ~ (p=1.000 n=10) ¹
TransformParse/complex         49.00 ± 0%   49.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                        16.09        16.09       +0.00%
¹ all samples are equal

Run, raw data and benchstat

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

Copy link
Copy Markdown

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


Review of PR #902: single-pass byte scan for L010

Verdict: No blocking issues. I read the diff of redundant_whitespace.go and the CHANGELOG, and traced the edge cases by hand. I did not run the tests or benchmarks, so the gate results are as reported in the PR description and the perf-gate comment.

Correctness

  • Indentation: runStart != 0 matches the old skip. A whitespace-only line is skipped by both versions. "\t x" is reported at column 2 by both.
  • Quotes: report(i) at an opening quote flushes the pending run, so runs never span a string. '' escapes toggle the state twice, as before.
  • Unterminated string: the final report(len(line)) is skipped when inString is true. Any earlier run was already flushed at the opening quote.
  • Invalid UTF-8: columns are now true byte offsets. This is intended and recorded under "Behavior changes" in the CHANGELOG. Valid UTF-8 gives the same columns as before.

Performance and design

  • Removing the regexp, the strings.Builder and the linePart slices explains the 0 allocs/op.
  • The perf gate passed. As the PR notes, the gated suite doesn't include LintRule/L010, so the target gain comes from the local M4 run only.
  • Optional: report is a closure created once per line. It doesn't allocate according to your numbers. Hoisting it out of the loop would make the code a bit easier to read, but it isn't needed.

Tests

  • The old implementation kept as an oracle, run over the corpus, the realworld statements and 300k random lines, is a strong check. The race/norace build-tag split is a reasonable way to keep -race runs short.
  • Suggestion (non-blocking): add a few hand-written table cases with fixed expected columns. Suggested cases are tab then spaces, a run right after a closing quote, an unterminated quote, and a whitespace-only line. If the oracle and the implementation ever change together, those cases would still catch a regression.
  • The benchmark corpora have no L010 violations, so the speedup is for clean input. The random-line differential test covers the violation path for correctness.

Security

No concerns. The scan is a bounded O(n) loop with no input-driven allocation.

Process

The W0-02 goldens now pass unchanged on the merged branch, which addresses my earlier concern about the invalid-UTF-8 column change showing up there.
· Branch: perf/w6-04-l010-byte-scan

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #902: single-pass byte scan for L010

Verdict: No blocking issues. I read the current redundant_whitespace.go. I did not run tests or benchmarks. Gate and perf numbers are as you reported them.

Correctness

  • Indentation: runStart != 0 skips a run at byte 0. Whitespace-only lines are skipped. "\t x" is reported at column 2.
  • Quotes: report(i) at an opening quote flushes the pending run, so runs never span a string. '' escapes toggle the state twice, as before.
  • Unterminated string: the final report(len(line)) does nothing inside a string. Your new comment explains why: runStart is already -1 there.
  • Invalid UTF-8: columns are now byte offsets, which is intended and recorded in the CHANGELOG.

Minor observations (non-blocking)

  • Fix and Check disagree on invalid UTF-8. fixLine (line ~176) still ranges over runes and calls WriteRune. That writes U+FFFD in place of each invalid byte. This was already the case before the PR, so it is not a regression. But Check is now byte-exact while Fix still rewrites invalid bytes. A byte-wise fixLine would make them consistent. A follow-up is fine.
  • Closure per line. report is created inside the line loop. Your 0 allocs/op shows it costs nothing, so hoisting it is optional.
  • Fixed-expectation tests. The differential test uses the old code as the oracle. A few table cases with hard-coded columns would protect against the oracle and the implementation drifting together. The PR notes the test file was changed to add named cases. Please check these cover tab then spaces, a run after a closing quote, an unterminated quote, and a whitespace-only line.

Performance

  • Removing the regexp, the strings.Builder and the linePart slices explains the 0 allocs. The amd64 gate does not cover LintRule/L010, as you note. The −68% figure comes from the local M4 run only.
  • Corpora with no violations measure clean input only. The violation path is unchanged.

Security

No concerns. The scan is a bounded O(n) loop and does not allocate based on input.

Tests

The differential test covers 129 corpus files, 3,002 statements and 300k random lines. The race/norace split keeps -race runs short. This is good coverage.
· Branch: perf/w6-04-l010-byte-scan

ajitpratap0 added a commit that referenced this pull request Nov 2, 2026
#896 (L007), #902 (L010) and #905 (lazy error wrap) target benchmarks the
PR-run gate never measured, so none of them could be accepted or closed
on an amd64 verdict. long_prefix is included so #905's expected miss is
still checked for regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
ajitpratap0 added a commit that referenced this pull request Nov 3, 2026
…w suite (#927)

* ci(perf): add BenchmarkLintRW to the shadow gate suite

PR #906's gate run moved 0 of 26 benchmarks because no linter benchmark was
gated. Add it so W6-05b, W6-06 and W6-07 are measured by the PR-run gate.

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

* ci(perf): gate LintRule L007/L010 and ParseError in the shadow suite

#896 (L007), #902 (L010) and #905 (lazy error wrap) target benchmarks the
PR-run gate never measured, so none of them could be accepted or closed
on an amd64 verdict. long_prefix is included so #905's expected miss is
still checked for regressions.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Resolve CHANGELOG (and gate-suite) conflicts by placing this PR's entries in main's Unreleased sections.

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


Review of PR #902: single-pass byte scan for L010

Verdict: No blocking issues. I read the current redundant_whitespace.go. I did not run tests or benchmarks, so the gate and perf numbers are as reported in the PR and the perf-gate comment.

Correctness

  • Indentation: runStart != 0 skips a run at byte 0, which matches the old behaviour. "\t x" reports column 2.
  • Quotes: report(i) at an opening quote flushes the pending run, so runs never span a string. Escaped '' toggles the state twice, as before.
  • Unterminated string: the final report(len(line)) is a no-op in a string, because runStart is already -1 there. The new comment says so.
  • Invalid UTF-8: columns are now byte offsets. This is intended and recorded in the CHANGELOG.

Non-blocking observations

  • Check and Fix disagree on invalid UTF-8. fixLine (lines 156-207) still ranges over runes and calls WriteRune. That rewrites each invalid byte as U+FFFD. This was already the case before the PR, so it isn't a regression. But Check is now byte-exact and Fix still alters the bytes. A byte-wise fixLine would make them consistent. A follow-up is fine.
  • Closure per line. report is created inside the line loop. The 0 allocs/op result shows it costs nothing, so hoisting it is optional.
  • Fixed-expectation tests. The differential test uses the old code as its oracle. The PR adds named column cases. Please check they cover tab then spaces, a run after a closing quote, an unterminated quote and a whitespace-only line. Those protect against the oracle and the implementation changing together.

Performance

  • Removing the regexp, the strings.Builder and the linePart slices explains the 0 allocs.
  • The amd64 gate doesn't include LintRule/L010, as the PR notes. The −68% figure comes from the local M4 run only, and it measures clean input. Merge still needs either a gate-suite extension or an explicit waiver.

Security

No concerns. The scan is a bounded O(n) loop with no input-driven allocation.

Tests

The differential test covers 129 corpus files, 3,002 statements and 300k random lines. The race/norace split keeps -race runs short.
· Branch: perf/w6-04-l010-byte-scan

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


Review of PR #902: single-pass byte scan for L010

Verdict: No blocking issues. I read the current redundant_whitespace.go. I did not run tests or benchmarks, so the gate and perf numbers are as reported in the PR and the perf-gate comment.

Correctness

  • Indentation: runStart != 0 skips a run at byte 0, which matches the old behaviour. Whitespace-only lines are skipped, and "\t x" reports column 2.
  • Quotes: report(i) at an opening quote flushes the pending run, so runs never span a string. Escaped '' toggles the state twice, as before.
  • Unterminated string: the final report(len(line)) is a no-op inside a string, because runStart is already -1 there. The comment at lines 125-126 says so.
  • Invalid UTF-8: columns are now byte offsets. This is intended and recorded in the CHANGELOG.

Non-blocking observations

  • Check and Fix disagree on invalid UTF-8. fixLine (redundant_whitespace.go:156-207) still ranges over runes and calls WriteRune, which rewrites each invalid byte as U+FFFD. This was already the case before the PR, so it isn't a regression. Check is now byte-exact while Fix still alters those bytes. A byte-wise fixLine would make them consistent. A follow-up is fine.
  • Closure per line. report is created inside the line loop (line 89). The 0 allocs/op result shows it costs nothing, so hoisting it is optional.
  • Fixed-expectation tests. The differential test uses the old code as its oracle. I did not open the test files in this pass. Please confirm the named column cases cover tab then spaces, a run after a closing quote, an unterminated quote and a whitespace-only line. Those protect against the oracle and the implementation changing together.

Performance

  • Removing the regexp, the strings.Builder and the linePart slices explains the 0 allocs.
  • The latest amd64 shadow gate does include LintRule/L010: -67.5% time (p<0.001), allocs 20758 → 0, B/op -100%, with the A/A control at 0 of 50 benchmarks over 3%. That meets the -50% target on amd64, so the "target not verified on amd64" caveat in the PR body is now out of date.
  • The LintRW/* benchmarks also improved: allocs fell about 6-7% and time about 3-6% on several dialects. Nothing in the gated suite regressed.
  • The corpora have no L010 violations, so these numbers measure clean input. The violation path is unchanged.

Security

No concerns. The scan is a bounded O(n) loop with no input-driven allocation.

Tests

The differential test covers 129 corpus files, 3,002 statements and 300k random lines. The race/norace split keeps -race runs short.
· Branch: perf/w6-04-l010-byte-scan

@ajitpratap0
ajitpratap0 merged commit ddbd4f9 into main Nov 3, 2026
64 of 66 checks passed

This branch was successfully deployed

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

Labels

documentation Improvements or additions to documentation linter performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant