Skip to content

feat(bench): per-arch baselines, matrix/pgo tasks, methodology doc [W0-12] - #917

Merged
ajitpratap0 merged 7 commits into
mainfrom
perf/w0-12-baselines-docs
Nov 1, 2026
Merged

ajitpratap0 merged 7 commits into
mainfrom
perf/w0-12-baselines-docs

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Test Plan

  • task check: pass
  • task test:cbinding: pass
  • go test -count=1 ./pkg/compatibility/...: pass
  • go run honnef.co/go/tools/cmd/staticcheck@latest ./scripts/perf/baseline ./pkg/sql/parser: clean
  • go test -race -count=1 ./scripts/perf/... ./pkg/gosqlx ./pkg/sql/parser: pass (TestPerformanceRegression passes)
  • New behaviour tests (scripts/perf/baseline/baseline_test.go): median parsing, alloc +1 caught, B/op exactly +2% allowed and +2.1% caught, 9x ns/op not gated, update-then-check round trip, injected alloc regression caught, empty baseline refused, committed files validate.
  • GOTOOLCHAIN=go1.26.9 task bench:baseline on arm64 right after the re-record: ok: 26 benchmarks.
  • Provenance fix round (bc2bf08): new TestUpdateFromInputDoesNotStampLocalToolchain (-update -input without -go-version is rejected; the given version is recorded).
  • Docs round (3b90f06): methodology now says the informational arm is default-GC -cpu 1 (-g), and -cpu 4 runs only in task bench:profile and the competitor harness; CHANGELOG cites feat(bench): per-arch baselines, matrix/pgo tasks, methodology doc [W0-12] #917.
  • task bench:matrix and task pgo:refresh smoke-run locally (generated default.pgo not committed).
  • Fix round (8ccc542): the check now fails as stale when a full measurement leaves a baseline entry unmeasured or finds a new one, and in every mode when no baseline benchmark was measured; -input rejects a goarch: header differing from -arch; a go_version major.minor mismatch warns. New tests: disjoint run fails, Verdict staleness, goarch mismatch, SameGoMinor. bench:matrix has pipefail and writes to gitignored .bench/matrix.txt.
  • task bench:gate (local, 10 rounds, merge-base vs HEAD): gate: verdict pass, to be read next to this PR's A/B perf gate (shadow) CI verdict (PLAN W0-12 acceptance: local gate reproduces CI).

Breaking Changes

None. performance_baselines.json keeps its shape (two unused entries removed, purpose field added).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

…0-12]

Add benchmarks/baselines/{amd64,arm64}.json (ns/op trend-only, B/op and
allocs/op gated) with a scripts/perf/baseline checker and -update flag,
task bench:matrix, bench:baseline, bench:baseline:update and pgo:refresh,
docs/PERFORMANCE_METHODOLOGY.md, and one consolidated wave-0 CHANGELOG
entry. performance_baselines.json and TestPerformanceRegression are
documented as a smoke test only; its two never-enforced entries are removed.

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

vercel Bot commented Nov 1, 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 1, 2026 5:38pm UTC

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #917 (per-arch baselines, tasks, methodology doc)

I read scripts/perf/baseline/baseline.go and main.go and did not run any tests. I did not open the Taskfile, the docs, the JSON files or baseline_test.go, so none of those are covered here.

Overall: the design is sound. It gates the deterministic metrics (allocs/op and B/op), keeps ns/op as trend-only, and refuses to check against an empty baseline. I found no blocking issues. The points below are things I'd consider before merging.

Potential issues

  1. A partial measurement can pass. Check treats a benchmark that is in the baseline but not measured as a note, not a failure (baseline.go:181-184). If a benchmark is renamed, or a suite line stops matching anything, its gate silently disappears. main.go:138 then prints ok: N benchmarks, where N excludes the missing ones. Consider adding a -strict flag for CI, or failing when Missing is non-empty on a full-suite run (that is, when -input is not used).
  2. go test failures are fatal but parse errors are quiet. In Parse, a field that fails ParseFloat is skipped (baseline.go:100-102). That is fine for custom metrics. However, a run where some lines lack -benchmem just drops those benchmarks, and the only visible effect is a "Missing" note.
  3. Custom b.ReportMetric units. The loop reads fields in pairs from index 2 and assumes value unit pairs. That holds for the standard output, but a benchmark that reports odd output could misalign the pairs. This is low risk.
  4. Sub-benchmark names with spaces. Go replaces spaces with _ in benchmark names, so strings.Fields is safe. The -\d+$ suffix regex would also strip a trailing -8 that belongs to a sub-benchmark name (for example Foo/size-8 run with -cpu 1, where Go adds no suffix). With -cpu 1, Go omits the -1 suffix, so a real name ending in -<digits> gets truncated. That could collide two keys, and the collision would merge their samples into one median. Passing -cpu 1 only strips nothing, so stripping should only happen when the suffix equals the -cpu value. Please confirm which case applies here.
  5. B/op tolerance when the base is small. 1e-9 epsilon plus 2% is fine for large values. For a baseline B/op of 0 or a few bytes, 2% allows no change, which is stricter than you may want. That matches the allocs/op rule, but is worth documenting.
  6. -update overwrites without a safety check. It rewrites the file even if the measured set is far smaller than the existing baseline, for example if one suite line fails to match. Warn, or require -force, when the new set drops benchmarks that the old file had.
  7. Cross-arch fallback. -arch differing from GOARCH errors unless -input is given. That is good. -update with -input lets someone record an amd64 baseline from arbitrary output, so GoVersion and Updated are then stamped from the local machine rather than the machine that produced the output. Minor, but the metadata can mislead.

Code quality

  • exitCode uses a direct type assertion. errors.As would be more idiomatic and would survive wrapping.
  • cmd.Output() captures stdout in memory for each package, which is fine. cmd.Stderr = log streams compile errors, which is helpful.
  • measure builds go test args from a suite file under the repo's control. No injection concern; it runs via exec.Command without a shell.
  • The measurement runs with GOGC=off, which makes B/op and allocs/op more deterministic. Good choice. Note that it also appends to os.Environ(), so an existing GOGC is overridden by the later entry as intended.

Tests and docs

Security

Nothing of concern: no shell interpolation, no network access, and file paths come from flags in a developer tool.
· branch perf/w0-12-baselines-docs

@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 69a5a728e8b1, head 817656f2f5ab. 10 interleaved rounds, benchtime 200ms, -cpu 1, pinned to CPU 3 (SMT siblings 2-3) with taskset.

  • no gated metric moved past its threshold

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.46% 0.364 -0.55% +0.00% 5 → 5 ok
ParseE2E/select_where -0.68% 0.472 -0.32% +0.00% 18 → 18 ok
ParseE2E/join -0.67% 0.677 +0.13% +0.00% 17 → 17 ok
ParseE2E/aggregate +0.46% 0.034 +0.17% +0.00% 18 → 18 ok
ParseE2E/subquery -0.18% 0.940 +0.13% +0.00% 14 → 14 ok
ParseE2E/cte +1.08% 0.088 +0.70% +0.00% 21 → 21 ok
ParseE2E/window +1.16% 0.010 -0.30% +0.00% 11 → 11 ok
ParseE2E/complex +0.59% 0.075 -0.07% +0.00% 50 → 50 ok
ParseE2E/insert +1.15% 0.007 +0.28% +0.00% 17 → 17 ok
ParseE2E/update +0.41% 0.426 +0.07% +0.00% 13 → 13 ok
ParseE2E/delete +1.41% 0.015 +0.78% +0.00% 5 → 5 ok
ParseRW/postgresql/medium +1.20% 0.052 +0.16% +0.00% 646 → 646 ok
ParseRW/mysql/medium +1.39% 0.015 +0.31% +0.00% 365 → 365 ok
ParseRW/mariadb/medium +0.79% 0.029 +0.38% +0.00% 523 → 523 ok
ParseRW/sqlserver/medium +1.12% 0.035 +0.45% +0.00% 936 → 936 ok
ParseRW/oracle/medium +1.03% 0.029 +0.11% +0.00% 1093 → 1093 ok
ParseRW/sqlite/medium +1.52% 0.003 +0.55% +0.00% 346 → 346 ok
ParseRW/snowflake/medium +0.84% 0.247 +0.06% +0.00% 777 → 777 ok
ParseRW/clickhouse/medium +0.45% 0.579 -0.86% +0.00% 384 → 384 ok
ParseFresh/simple_select -0.15% 0.850 -0.20% +0.00% 6 → 6 ok
ParseFresh/join -0.67% 0.436 -0.76% +0.00% 18 → 18 ok
ParseFresh/complex +0.11% 0.971 -0.65% +0.00% 51 → 51 ok
PoolGetPut/serial -0.02% 0.971 -0.02% +0.00% 0 → 0 ok
Tokenize/scratch/join -0.28% 0.596 -0.03% +0.00% 0 → 0 ok
Tokenize/scratch/complex -0.18% 0.791 -0.48% +0.00% 0 → 0 ok
Tokenize/scratch/insert -1.86% 0.218 -0.84% +0.00% 0 → 0 ok
geomean +0.40% 0.143 -0.03%

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

Instructions per operation (cmd/perfdriver)

case instructions/op status
e2e/aggregate +0.03% ok
e2e/complex -0.00% ok
e2e/cte -0.05% ok
e2e/delete +0.03% ok
e2e/insert +0.01% ok
e2e/join +0.00% ok
e2e/select_where +0.03% ok
e2e/simple_select -0.06% ok
e2e/subquery +0.00% ok
e2e/update -0.02% ok
e2e/window +0.06% ok
rw/postgresql/medium -0.06% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select +0.63% 0.677
ParseE2E/select_where -1.05% 0.186
ParseE2E/join +0.06% 0.910
ParseE2E/aggregate -0.28% 0.705
ParseE2E/subquery -0.05% 0.734
ParseE2E/cte -0.05% 0.912
ParseE2E/window +1.69% 0.015
ParseE2E/complex +0.30% 0.427
ParseE2E/insert +0.23% 0.345
ParseE2E/update -0.05% 0.705
ParseE2E/delete -0.09% 0.880
ParseRW/postgresql/medium +0.29% 0.529
ParseRW/mysql/medium +0.36% 0.353
ParseRW/mariadb/medium -0.35% 1.000
ParseRW/sqlserver/medium +0.55% 0.315
ParseRW/oracle/medium +1.08% 0.105
ParseRW/sqlite/medium +0.05% 0.623
ParseRW/snowflake/medium +1.49% 0.043
ParseRW/clickhouse/medium +1.21% 0.190
ParseFresh/simple_select +0.45% 0.450
ParseFresh/join -0.36% 0.796
ParseFresh/complex -0.10% 0.739
PoolGetPut/serial -0.01% 0.436
Tokenize/scratch/join -0.76% 0.520
Tokenize/scratch/complex -0.29% 0.650
Tokenize/scratch/insert -1.00% 0.063
geomean +0.15%
Environment
date:        2026-11-01T17:40:11Z
base:        69a5a728e8b1e87851ffeb723b77a1bd5572ad73
head:        working tree of 817656f2f5abf41dffb2a9e907f94d2752c5abac
go:          go version go1.26.1 linux/amd64
kernel:      Linux 7.0.0-1017-azure x86_64
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
clocksource: tsc
load:        1.25, 0.35, 0.12
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 9V45 96-Core Processor
Thread(s) per core:                      2
Core(s) per socket:                      2
Socket(s):                               1
Hypervisor vendor:                       Microsoft
L1d cache:                               96 KiB (2 instances)
L1i cache:                               64 KiB (2 instances)
L2 cache:                                2 MiB (2 instances)
L3 cache:                                32 MiB (1 instance)
benchstat (primary metric)
goos: linux
goarch: amd64
pkg: github.com/ajitpratap0/GoSQLX/pkg/gosqlx
cpu: AMD EPYC 9V45 96-Core Processor                
                          │    base     │                head                │
                          │   sec/op    │   sec/op     vs base               │
ParseE2E/simple_select      1.190µ ± 1%   1.195µ ± 2%       ~ (p=0.363 n=10)
ParseE2E/select_where       2.337µ ± 1%   2.321µ ± 2%       ~ (p=0.469 n=10)
ParseE2E/join               3.526µ ± 4%   3.502µ ± 1%       ~ (p=0.670 n=10)
ParseE2E/aggregate          2.584µ ± 1%   2.596µ ± 3%       ~ (p=0.030 n=10)
ParseE2E/subquery           2.279µ ± 1%   2.275µ ± 1%       ~ (p=0.926 n=10)
ParseE2E/cte                3.063µ ± 1%   3.096µ ± 1%       ~ (p=0.085 n=10)
ParseE2E/window             1.986µ ± 1%   2.009µ ± 1%  +1.16% (p=0.007 n=10)
ParseE2E/complex            9.081µ ± 1%   9.135µ ± 1%       ~ (p=0.075 n=10)
ParseE2E/insert             1.613µ ± 1%   1.631µ ± 1%  +1.15% (p=0.005 n=10)
ParseE2E/update             1.451µ ± 2%   1.457µ ± 2%       ~ (p=0.424 n=10)
ParseE2E/delete             863.2n ± 1%   875.4n ± 4%       ~ (p=0.015 n=10)
ParseRW/postgresql/medium   110.7µ ± 8%   112.0µ ± 2%       ~ (p=0.052 n=10)
ParseRW/mysql/medium        59.35µ ± 2%   60.17µ ± 2%       ~ (p=0.015 n=10)
ParseRW/mariadb/medium      85.48µ ± 2%   86.15µ ± 2%       ~ (p=0.029 n=10)
ParseRW/sqlserver/medium    161.3µ ± 1%   163.2µ ± 1%       ~ (p=0.035 n=10)
ParseRW/oracle/medium       197.3µ ± 1%   199.3µ ± 1%       ~ (p=0.029 n=10)
ParseRW/sqlite/medium       51.65µ ± 2%   52.43µ ± 3%  +1.52% (p=0.003 n=10)
ParseRW/snowflake/medium    121.3µ ± 3%   122.3µ ± 1%       ~ (p=0.247 n=10)
ParseRW/clickhouse/medium   57.39µ ± 3%   57.65µ ± 1%       ~ (p=0.579 n=10)
ParseFresh/simple_select    6.840µ ± 1%   6.829µ ± 2%       ~ (p=0.839 n=10)
ParseFresh/join             14.58µ ± 8%   14.49µ ± 2%       ~ (p=0.436 n=10)
ParseFresh/complex          21.96µ ± 1%   21.98µ ± 2%       ~ (p=0.971 n=10)
PoolGetPut/serial           62.60n ± 1%   62.58n ± 1%       ~ (p=0.971 n=10)
geomean                     8.827µ        8.876µ       +0.55%

                          │     base     │                head                 │
                          │     B/s      │     B/s       vs base               │
ParseE2E/simple_select      20.84Mi ± 1%   20.76Mi ± 2%       ~ (p=0.343 n=10)
ParseE2E/select_where       42.03Mi ± 1%   42.32Mi ± 2%       ~ (p=0.481 n=10)
ParseE2E/join               34.08Mi ± 4%   34.31Mi ± 1%       ~ (p=0.684 n=10)
ParseE2E/aggregate          31.00Mi ± 1%   30.86Mi ± 3%       ~ (p=0.034 n=10)
ParseE2E/subquery           34.32Mi ± 1%   34.38Mi ± 1%       ~ (p=0.898 n=10)
ParseE2E/cte                38.61Mi ± 1%   38.20Mi ± 1%       ~ (p=0.089 n=10)
ParseE2E/window             45.14Mi ± 1%   44.62Mi ± 1%  -1.15% (p=0.006 n=10)
ParseE2E/complex            39.70Mi ± 1%   39.46Mi ± 1%       ~ (p=0.075 n=10)
ParseE2E/insert             44.93Mi ± 1%   44.43Mi ± 1%  -1.11% (p=0.005 n=10)
ParseE2E/update             36.15Mi ± 2%   36.00Mi ± 2%       ~ (p=0.448 n=10)
ParseE2E/delete             57.45Mi ± 1%   56.65Mi ± 3%       ~ (p=0.015 n=10)
ParseRW/postgresql/medium   32.42Mi ± 8%   32.04Mi ± 2%       ~ (p=0.045 n=10)
ParseRW/mysql/medium        45.61Mi ± 2%   44.98Mi ± 2%       ~ (p=0.016 n=10)
ParseRW/mariadb/medium      41.48Mi ± 2%   41.16Mi ± 1%       ~ (p=0.025 n=10)
ParseRW/sqlserver/medium    41.24Mi ± 1%   40.78Mi ± 1%       ~ (p=0.035 n=10)
ParseRW/oracle/medium       42.48Mi ± 1%   42.05Mi ± 1%       ~ (p=0.027 n=10)
ParseRW/sqlite/medium       40.00Mi ± 2%   39.40Mi ± 3%  -1.50% (p=0.003 n=10)
ParseRW/snowflake/medium    41.92Mi ± 3%   41.58Mi ± 1%       ~ (p=0.225 n=10)
ParseRW/clickhouse/medium   39.27Mi ± 3%   39.09Mi ± 1%       ~ (p=0.592 n=10)
geomean                     38.68Mi        38.41Mi       -0.70%

                          │      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               2.305Ki ± 0%     2.305Ki ± 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            6.398Ki ± 0%     6.398Ki ± 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   70.50Ki ± 0%     70.50Ki ± 0%       ~ (p=0.582 n=10)
ParseRW/mysql/medium        43.72Ki ± 0%     43.72Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/mariadb/medium      64.83Ki ± 0%     64.83Ki ± 0%       ~ (p=0.261 n=10)
ParseRW/sqlserver/medium    90.59Ki ± 0%     90.59Ki ± 0%       ~ (p=0.103 n=10)
ParseRW/oracle/medium       113.8Ki ± 0%     113.8Ki ± 0%       ~ (p=0.458 n=10)
ParseRW/sqlite/medium       39.89Ki ± 0%     39.89Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/snowflake/medium    83.38Ki ± 0%     83.38Ki ± 0%       ~ (p=1.000 n=10)
ParseRW/clickhouse/medium   37.13Ki ± 0%     37.13Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/simple_select      896.0 ± 0%       896.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/join             2.383Ki ± 0%     2.383Ki ± 0%       ~ (p=1.000 n=10) ¹
ParseFresh/complex          6.477Ki ± 0%     6.477Ki ± 0%       ~ (p=1.000 n=10) ¹
PoolGetPut/serial             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                 │
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             50.00 ± 0%      50.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         365.0 ± 0%      365.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/mariadb/medium       523.0 ± 0%      523.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/sqlserver/medium     936.0 ± 0%      936.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/oracle/medium       1.093k ± 0%     1.093k ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/sqlite/medium        346.0 ± 0%      346.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/snowflake/medium     777.0 ± 0%      777.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseRW/clickhouse/medium    384.0 ± 0%      384.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           51.00 ± 0%      51.00 ± 0%       ~ (p=1.000 n=10) ¹
PoolGetPut/serial            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                │
                          │  sec/stmt   │  sec/stmt    vs base               │
ParseRW/postgresql/medium   9.222µ ± 8%   9.332µ ± 2%       ~ (p=0.050 n=10)
ParseRW/mysql/medium        7.419µ ± 2%   7.522µ ± 2%       ~ (p=0.016 n=10)
ParseRW/mariadb/medium      9.498µ ± 2%   9.573µ ± 2%       ~ (p=0.029 n=10)
ParseRW/sqlserver/medium    7.683µ ± 1%   7.769µ ± 1%       ~ (p=0.035 n=10)
ParseRW/oracle/medium       8.222µ ± 1%   8.306µ ± 1%       ~ (p=0.030 n=10)
ParseRW/sqlite/medium       8.608µ ± 2%   8.739µ ± 3%  +1.52% (p=0.003 n=10)
ParseRW/snowflake/medium    9.329µ ± 3%   9.407µ ± 1%       ~ (p=0.247 n=10)
ParseRW/clickhouse/medium   8.199µ ± 3%   8.236µ ± 1%       ~ (p=0.579 n=10)
geomean                     8.491µ        8.579µ       +1.04%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │     base     │                head                │
                         │    sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.608µ ±  2%   1.604µ ± 1%       ~ (p=0.590 n=10)
Tokenize/scratch/complex   4.176µ ±  1%   4.168µ ± 1%       ~ (p=0.796 n=10)
Tokenize/scratch/insert    804.4n ± 11%   789.4n ± 2%       ~ (p=0.218 n=10)
geomean                    1.754µ         1.741µ       -0.78%

                         │     base      │                head                 │
                         │      B/s      │     B/s       vs base               │
Tokenize/scratch/join      74.73Mi ±  2%   74.93Mi ± 1%       ~ (p=0.684 n=10)
Tokenize/scratch/complex   86.33Mi ±  1%   86.49Mi ± 1%       ~ (p=0.725 n=10)
Tokenize/scratch/insert    90.11Mi ± 10%   91.82Mi ± 2%       ~ (p=0.218 n=10)
geomean                    83.46Mi         84.11Mi       +0.78%

                         │     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

Run, raw data and benchstat

… matrix, doc fixes [W0-12]

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


Review of PR #917 (per-arch baselines, tasks, methodology doc)

I read scripts/perf/baseline/baseline.go and main.go at the current head (8ccc542). I did not run the tests, and I did not open the Taskfile, the docs, the JSON files or baseline_test.go. The perf-gate comment shows PASS in shadow mode with no allocs/op or B/op change, which is expected for a PR that doesn't touch the parser.

Overall: the design is sound and I found no blocking issues. It gates the deterministic metrics (allocs/op and B/op), keeps ns/op trend-only, refuses an empty baseline, and rejects a goarch mismatch in recorded input.

Corrections to the earlier automated review

  • "A partial measurement can pass" no longer applies. Report.Verdict(full) (baseline.go:176) fails on any Missing or New entry when the suite was measured live (main.go:149 passes *input == ""). It also fails when Compared == 0. Only the -input path tolerates missing entries, and that looks intentional.
  • The ParseFloat skip at baseline.go:100 is harmless. If a line lacks any of ns/op, B/op or allocs/op, it is dropped (:113). Measuring the suite live then fails as stale, because the benchmark shows up as Missing.

Potential issues

  1. procSuffix can mangle names under -cpu 1 (baseline.go:66, :95). Go appends -N to a benchmark name only when GOMAXPROCS is not 1. Because the suite always runs with -cpu 1, there is no suffix to strip, but -\d+$ still strips a real trailing -<digits>. For example, a sub-benchmark named size-8 becomes size, and sibling cases such as size-8 and size-16 would collapse into one key and have their samples merged into one median. No current gated benchmark seems to hit this, so it is latent. A fix is to strip only the -N that matches the run's -cpu value. When the value is 1, strip nothing. The -input path would need the value passed in.
  2. -update can silently shrink the baseline (main.go:107-118). If a suite line stops matching, -update writes a smaller file and prints the new count. A warning, or a -force requirement, when the old file's benchmarks are not a subset of the new ones would catch this. The next live check would pass, because Missing is computed against the new file.
  3. -update -input stamps local metadata (main.go:108-109). GoVersion and Updated come from the local machine, not from whoever produced the output. That could mislead the SameGoMinor warning, for example when amd64 CI output is recorded from an arm64 laptop. Either read the version from the output, or leave the field empty when -input is used.
  4. The B/op tolerance is multiplicative (baseline.go:238). A baseline of 0 B/op allows no growth, and a baseline of 50 B/op allows 1 byte. That is consistent with the intent and probably desirable for the zero-alloc tokenizer cases. A one-line note in PERFORMANCE_METHODOLOGY.md would help.
  5. Missing -benchmem on one line is a vague failure. Parse drops benchmarks without all three metrics. The "no benchmarks … (was -benchmem set?)" error only fires when nothing at all parses.

Code quality

  • exitCode (main.go:48) uses a direct type assertion. errors.As would survive wrapping.
  • measure runs go test via exec.Command with no shell, and the suite file is repo-controlled, so there is no injection concern. GOGC=off is appended after os.Environ(), so it overrides any existing value.
  • rep.TimeDrift uses a hard-coded 25%. Consider a named constant next to BytesTolerance.

Tests

The test plan covers the important edges: median parsing, the ±2% boundary, an empty baseline, and the update-then-check round trip. I'd add two cases:

  • A name ending in -<digits> under -cpu 1 (item 1).
  • An -update that drops benchmarks, if you add the guard in item 2.

Housekeeping

The CHANGELOG entry still needs #910 and #914 appended when they merge. Consider a checklist item on those PRs so it isn't forgotten.

Security

Nothing of concern: no network access, no shell interpolation, and the paths come from developer-tool flags.
· branch perf/w0-12-baselines-docs

…y docs [W0-12]

-update -input now requires -go-version and records external provenance
instead of stamping the local toolchain. Re-record arm64.json with go1.26.9,
drop the stale tolerance advice from PERFORMANCE_TUNING, and state that
bench:baseline is local until W0-13.

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


Review of PR #917 (per-arch baselines, tasks, methodology doc)

I re-read scripts/perf/baseline/baseline.go and main.go at the current head. I did not run the tests, and I did not open the Taskfile, docs, JSON files or baseline_test.go.

Overall: I found no blocking issues. Allocs/op and B/op are gated and ns/op is trend-only. An empty baseline is refused. A live run fails when the baseline is stale, and -input is checked against the baseline's arch. The shadow-mode perf gate passes with no allocs/op or B/op change.

Several points from the earlier automated reviews are already handled in the code:

  • Report.Verdict(full) (baseline.go:176) fails on Missing or New entries for a live run, and when Compared == 0.
  • exitCode already uses errors.As.
  • -update -input requires -go-version and records the declared toolchain, not the local one (main.go:76, :119).

Remaining issues

  1. procSuffix can mangle names (baseline.go:66, :95). With -cpu 1, Go adds no -N suffix. -\d+$ therefore only strips a real trailing -<digits> from a sub-benchmark name. For example, Foo/size-8 and Foo/size-16 would both become Foo/size, and their samples would be merged into one median. No gated benchmark hits this today, so it is latent. Fix: strip only a suffix that matches the run's -cpu value, and strip nothing when it is 1. This needs an extra argument for the -input path.
  2. -update can silently shrink the baseline (main.go:116-134). If a suite line stops matching, the smaller set is written and the next check passes, because Missing is computed against the new file. Consider reading the existing file first and refusing, or requiring -force, when benchmarks would be dropped.
  3. Lines missing a metric are dropped quietly (baseline.go:113). The "was -benchmem set?" error only fires when nothing parses. In -input mode (full == false), a partially dropped line only shows up as a "not measured" note, so it still passes. This is minor and probably intended for -input.
  4. Small nits.
    • The 25% TimeDrift threshold (baseline.go:241) is a magic number. A named constant next to BytesTolerance would be clearer.
    • The B/op tolerance is multiplicative. A baseline of 0 B/op therefore allows no growth, and 50 B/op allows 1 byte. This is the right behaviour for the zero-alloc tokenizer cases, but one line in PERFORMANCE_METHODOLOGY.md would document it.
    • In measure, the failure message could include the stderr tail. It already streams to log, so this is optional.

Tests

I'd add two cases:

  • A name ending in -<digits> under -cpu 1 (issue 1).
  • An -update that drops benchmarks, if you add the guard from issue 2.

Housekeeping

The CHANGELOG entry still needs #910 and #914 appended when they merge.

Security

Nothing of concern: exec.Command runs without a shell, the suite file is repo-controlled, and there is no network access.
· branch perf/w0-12-baselines-docs

…n CHANGELOG

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


Review of PR #917 (per-arch baselines, tasks, methodology doc)

I read scripts/perf/baseline/baseline.go and main.go at the current head. I did not run the tests, and I did not open the Taskfile, docs, JSON files or baseline_test.go. The shadow-mode perf gate passes with no allocs/op or B/op change.

Overall: I found no blocking issues. The design gates the deterministic metrics (allocs/op and B/op) and keeps ns/op as trend-only. An empty baseline is refused. A live run fails when the baseline is stale. -input is checked against the baseline's arch.

Some points from the earlier automated reviews no longer apply, or never did:

  • Partial measurement can pass: Verdict(full) (baseline.go:176) fails on Missing or New entries for live runs, and when Compared == 0.
  • Local metadata stamped on -update -input: -go-version is required and recorded (main.go:76, :122).
  • exitCode type assertion: it already uses errors.As.

Remaining issues

  1. procSuffix can merge distinct benchmarks (baseline.go:66, :95). The suite runs with -cpu 1, where Go adds no -N suffix. The regex -\d+$ therefore only strips a real trailing -<digits> from a sub-benchmark name. Foo/size-8 and Foo/size-16 would both become Foo/size, and their samples would be merged into one median. No gated benchmark hits this today, so it is latent. Fix: strip only a suffix that matches the run's -cpu value, and strip nothing when it is 1. The -input path would need that value passed in, for example as a flag.
  2. -update can silently shrink the baseline (main.go:116-134). If a suite line stops matching, the smaller set is written, and the next check passes because Missing is computed against the new file. Consider reading the existing file first and refusing, or requiring -force, when the update would drop benchmarks.
  3. Lines missing a metric are dropped quietly (baseline.go:113). Live runs still fail as stale. In -input mode a dropped line only produces a "not measured" note, which is probably intended.
  4. Nits.
    • The 25% TimeDrift threshold (baseline.go:241) is a magic number. A named constant next to BytesTolerance would be clearer.
    • The B/op tolerance is multiplicative, so a baseline of 0 B/op allows no growth. That suits the zero-alloc tokenizer cases. One line in PERFORMANCE_METHODOLOGY.md would document it.
    • measure could include the tail of stderr in its failure error. It currently only streams stderr to the log.

Tests

I'd add two cases:

  • A name ending in -<digits> under -cpu 1, for issue 1.
  • An -update that drops benchmarks, if you add the guard from issue 2.

Housekeeping

The CHANGELOG entry still needs #910 and #914 appended when they merge.

Security

Nothing of concern. exec.Command runs without a shell, the suite file is repo-controlled, and there is no network access.
· branch perf/w0-12-baselines-docs

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #917 (per-arch baselines, tasks, methodology doc)

I read scripts/perf/baseline/baseline.go and main.go at the current head. I did not run the tests, and I did not open the Taskfile, docs, JSON files or baseline_test.go. The shadow-mode perf gate passes, with no allocs/op or B/op change.

Overall: I found no blocking issues. The design gates allocs/op and B/op, keeps ns/op trend-only, and refuses an empty baseline. A live run fails when the baseline is stale. -input is checked against the baseline's arch, and -update -input requires -go-version.

Already handled (no action needed)

  • A partial live measurement cannot pass. Verdict(full) (baseline.go:176) fails on Missing or New entries, and when Compared == 0.
  • exitCode already uses errors.As.
  • -update -input records the declared toolchain, not the local one (main.go:76, :122).

Remaining issues

  1. procSuffix can merge distinct benchmarks (baseline.go:66, :95). The suite runs with -cpu 1, where Go adds no -N suffix. The regex -\d+$ therefore only strips a real trailing -<digits> from a sub-benchmark name. Foo/size-8 and Foo/size-16 would both become Foo/size, and their samples would be merged into one median. No gated benchmark hits this today, so it is latent. Fix: strip only a suffix that equals the run's -cpu value, and strip nothing when it is 1. The -input path would need that value, for example as a flag.
  2. -update can silently shrink the baseline (main.go:116-134). If a suite line stops matching, the smaller set is written. The next check then passes, because Missing is computed against the new file. Consider reading the existing file first and refusing, or requiring -force, when the update would drop benchmarks.
  3. Lines missing a metric are dropped quietly (baseline.go:113). Live runs still fail as stale. In -input mode a dropped line only produces a "not measured" note, which is probably intended.

Nits

  • The 25% TimeDrift threshold (baseline.go:241) is a magic number. A named constant next to BytesTolerance would be clearer.
  • The B/op tolerance is multiplicative, so a baseline of 0 B/op allows no growth. That suits the zero-alloc tokenizer cases. One line in PERFORMANCE_METHODOLOGY.md would document it.
  • measure could include the tail of stderr in its failure error. Right now stderr only streams to the log.

Tests

I'd add two cases:

  • A benchmark name ending in -<digits> under -cpu 1, for issue 1.
  • An -update that drops benchmarks, if you add the guard from issue 2.

Security

Nothing of concern. exec.Command runs without a shell, the suite file is repo-controlled, and there is no network access.
· branch perf/w0-12-baselines-docs

@ajitpratap0
ajitpratap0 merged commit 11f55dd into main Nov 1, 2026
48 checks passed
ajitpratap0 added a commit that referenced this pull request Nov 1, 2026
Resolves the CHANGELOG.md [Unreleased] conflict with #917 (W0-12) by
keeping both its ### Added block and the W0-15 ### Changed block.

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 1, 2026
…trix reports [W0-15]

Review fixes after merging #917 (W0-12):

- task bench:matrix sets GOMEMLIMIT=64MiB next to GOGC=off; the GOMEMLIMIT
  lint now understands YAML env blocks with the keys on separate lines.
- docs/PERFORMANCE_METHODOLOGY.md documents GOMEMLIMIT and its effects
  (GC share in time and Ir/op, pool drains), the pinned PERF_GO_VERSION,
  the matrix and profile (cachegrind) dispatch modes and ratio-only reports.
- scripts/perf/baseline keeps GOGC=off without GOMEMLIMIT on purpose (it
  gates absolute allocs/op and B/op, exact only without pool drains), drops
  an inherited GOGC/GOMEMLIMIT, and is covered by the lint through an
  explicit, file-restricted exemption marker.
- ab.sh comment on allocs/op corrected: the limit lets GC and pool drains
  run; allocs/op stays exact for the gated suite only.
- Matrix dispatch: job "Matrix A/B (report only)", report heading
  "Matrix A/B: VERDICT (report only, not a gate verdict)" via ab.sh -T and
  gate -report-only/-title.
- PERF_GOMEMLIMIT / the gomemlimit input are validated up front
  (perf_check_gomemlimit) instead of every benchmark binary panicking.
- ab.sh die() no longer appends the exit code to its message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@ajitpratap0
ajitpratap0 deleted the perf/w0-12-baselines-docs branch November 1, 2026 18:43

This branch was successfully deployed

1 active deployment
Preview — dcd492cb Deployed Nov 1, 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 parser performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant