Skip to content

ci: unblock and harden the benchmark job - #437

Open
David Levy (dlevy-msft-sql) wants to merge 45 commits into
mainfrom
ci/pin-benchstat
Open

David Levy (dlevy-msft-sql) wants to merge 45 commits into
mainfrom
ci/pin-benchstat

Conversation

@dlevy-msft-sql

@dlevy-msft-sql David Levy (dlevy-msft-sql) commented Aug 21, 2026 •

Copy link
Copy Markdown

The benchmarks job was failing on every branch in the repo, and fixing that surfaced four further defects in the same job. All of them share a shape: a step that could not do its work reported success, or failed with no way to tell why.

Touches .github/workflows/pr-validation.yml and adds internal/benchgate, a small Go command that holds the regression-gate logic. Section 5 explains why the gate moved out of Bash.

benchmarks is a required check on main, so both directions matter: a false failure blocks every merge, and a false pass lets a regression through unnoticed.

1. The job failed on every branch

go: golang.org/x/perf/cmd/benchstat@latest: golang.org/x/perf@v0.0.0-20260819171926-ebcb4798430d
   requires go >= 1.26.0 (running go 1.25.7; GOTOOLCHAIN=local)

golang.org/x/perf moved its go directive to 1.26.0 on 2026-08-19 in ebcb4798430d. Nothing in our code changed. Confirmed the same failing step across unrelated branches — dependabot, release-please, dev/saurabh/fix-tx-query-hang.

Worth understanding why only that step broke, since the benchmark runs on either side of it were fine. Same toolchain, same directory:

Command Constrained by Result
go build ./... our go.mod → go 1.25.0 exit 0
go test -bench ... our go.mod → go 1.25.0 exit 0
go install golang.org/x/perf/cmd/benchstat@latest x/perf's go.mod → go 1.26.0 exit 1

go install pkg@version runs in an empty main module, so our go directive is irrelevant and the tool's own requirement governs. That asymmetry is permanent and cannot be designed away — so this PR makes it cheap instead.

Fix: install benchstat in its own step immediately after Setup go, rather than 46 minutes into the job. Measured from a real run:

 7. Run baseline benchmarks (main) | 23 min
 8. Run PR benchmarks             | 23 min
 9. Compare benchmarks            |  0 min   <- the install lived here

Failure now surfaces at about minute one. benchstat stays on @latest: with the fail-fast ordering plus the fail-closed gate below, a pin buys nothing that isn't already covered, and @latest surfaces upstream breakage while it is still cheap to fix. #439 moved this job to Go 1.27, so the original incompatibility is resolved either way.

2. The regression gate failed open

Check for regressions inferred "no regression" from "my regex matched nothing". Those are different statements and it could not tell them apart. There was a guard, but it tested whether bench_diff.txt exists, not whether it contains anything.

Running the shipped step against fixtures, before this PR:

Input Result
Real 22% regression blocks
Regression printed +22% instead of +22.00% passes
Empty bench_diff.txt passes
benchstat: no data for bench_old.txt passes
Any change to benchstat's row formatting passes

Two of those were live gaps, not hypothetical drift: the regex hardcoded a decimal point, and an empty file cleared the [ ! -f ] guard.

Fix: the gate now parses benchstat's -format=csv output with encoding/csv and refuses to report "clean" from output it could not interpret. Unreadable rows, header drift and an empty comparison are all errors.

3. A clean sweep of improvements aborted the job

Found while testing #2. REGRESSED=$(...) aborts under set -eo pipefail when the final grep matches nothing. Typical runs contain small +0.38% entries so the grep matched and this stayed hidden, but a PR where every benchmark improved or was ~ failed with no message.

The first attempt at this wrapped the whole pipeline in || true, which @copilot-pull-request-reviewer correctly flagged as reintroducing the very fail-open being fixed — it also swallowed grep exit 2 and any awk failure.

Fix: superseded by section 5. "No regressions" is now a value the gate returns, not the absence of a pattern match, so the distinction that caused this cannot arise.

4. SQL Server never started, and nothing said so

Run 32585542809 failed with nothing but Process completed with exit code 1. Three gaps hid the cause:

Waiting for SQL Server... (attempt 29/30)
Waiting for SQL Server... (attempt 30/30)
  • The readiness loop had no result flag, so it fell through after 30 failures and the step reported success.
  • The warmup then ran against a dead server with > /dev/null 2>&1, so the failure carried no diagnosis.
  • Show SQL Server logs on failure existed only in the build job, so no container logs were captured either.

Fix: the build job already gets the first and third right, so this applies that existing pattern to benchmarks rather than inventing one, and keeps warmup output in a file that is tailed on failure.

Container memory: the loop was commented as 60 seconds, but sqlcmd burns ~8s per attempt when nothing is listening, so it actually waited 295 seconds and SQL 2025 still had not started. The cause was docker run -m 2GB — the documented minimum for SQL Server 2022 and later, with no headroom. Both jobs now run -m 4GB with MSSQL_MEMORY_LIMIT_MB=3072.

Microsoft's container memory guidance requires MSSQL_MEMORY_LIMIT_MB to sit below the container limit, and recommends reserving 10-20% of it for the OS and auxiliary processes. 4096 MiB with a 3072 MiB engine limit leaves 1024 MiB, or 25%. Left unset the engine takes 80% of the cgroup limit (3277 MB), so the explicit value is marginally tighter than the default. ubuntu-latest on a public repo has 16 GB, so 4 GiB does not crowd the toolchain.

The readiness comment now describes the loop in attempts rather than wall time. The real bound is 30 × (connect timeout + 2s), and the abort message is what an operator reads when the job fails, so claiming 60 seconds sends them after the wrong problem.

OOM diagnostics: a cgroup memory kill reaches the test process as connection resets and unexpected EOF. In a TDS driver suite that is indistinguishable from a protocol bug. The Show SQL Server logs on failure step in both jobs now reports container state before dumping the logs, and names the memory case:

sqlserver: oom-killed=true exit=137 status=exited memory-limit=4096 MiB
::error::SQL Server was OOM-killed. Connection errors above are a symptom of the
container memory limit, not of the driver. Raise -m and MSSQL_MEMORY_LIMIT_MB
together, keeping 10-20% headroom between them.

Exit 137 without the OOM flag is a warning rather than an error, since nothing in these jobs sends SIGKILL deliberately. Both readiness loops exit 1 into this same step, so a kill during startup and a kill mid-run are both covered.

5. The gate moved from Bash to Go

The gate started as a shell script embedded in YAML. Over five review iterations it accumulated nine defects, every one found by review rather than by the job itself:

  1. || true masking grep exit 2
  2. cp ... 2>/dev/null || true swallowing real copy errors
  3. ^Benchmark(Parent/Sub)$ matching nothing — go test splits -bench at /, so a sub-benchmark must be addressed through its parent
  4. A 20% speedup failing the build — SetBytes renders it as -20% sec/op and +25% B/s, and any-positive-delta called the second one a regression
  5. geomean reaching the selector as if it were a benchmark
  6. The row count failing open on header drift, by counting rows the detector could not interpret
  7. Confirmation keyed on benchmark name only, so a different metric could confirm a regression
  8. awk -F, is not a CSV parser — "Foo/size=1,024-4" shifted fields, silently dropping a regression while the row count stayed healthy
  9. No test anywhere in the repo covering any of it

Each individual fix was reachable in Bash. The pattern was not: this is a required check whose failure mode is silence, and it had no tests, so every defect above would have shipped green.

internal/benchgate is that logic as a Go package, and the workflow step is now 61 lines of YAML that builds and calls it:

Parse encoding/csv, unit-keyed; fails closed on header drift and unreadable deltas
Regressions / Improvements threshold applied in the correct direction per unit
Selector collapses sub-benchmarks to the parent, regexp.QuoteMeta, excludes geomean
Confirm / Unmeasured intersects the recheck on name and unit, and refuses to clear a candidate that was never remeasured

Review of the Go version then found three more fail-open defects, all fixed here. Each is worth naming, because all three are the same bug class this PR set out to remove — inferring "clean" from an absence:

  • Throughput regressions were never flagged (4cb4b23). Regressions gated on lowerIsBetter, so a significant -20% B/s collapse could not be reported. I had fixed the false-positive direction of Message queue implementation misses some data and messages #4 and left the false-negative direction open. The threshold now applies in both directions.
  • A malformed row could hide behind a valid one (4cb4b23). Skipping an unreadable delta only failed closed when every row was unreadable; one clean row alongside a malformed regression left a non-empty result and reported success. That is Scale and precision are zero for all datetime types #8's exact failure mode. An unreadable delta under a recognised table is now a parse error.
  • An unevaluated candidate was dismissed as noise (20e03bf). Because Selector addresses a flagged sub-benchmark through its parent, the recheck returns rows for siblings. If the candidate itself never produced a comparable row, Confirm returned empty and the gate treated it as runner noise — on a clean exit. Unmeasured now reports flagged keys absent from the recheck, and those fail as unevaluated. Presence counts regardless of significance: a flagged row coming back ~ genuinely is noise; only absence is unevaluated.

Testing

go test ./internal/benchgate/ runs with everything else. 99.4% of statements; the only uncovered statement is os.Exit(run(os.Args[1:], os.Stdout)), which has no logic in it — run(args []string, out io.Writer) int was extracted precisely so dispatch, usage and error reporting are all exercised.

The regression cases from the original Bash harness are preserved as Go tests, alongside one for each defect above:

TestParseHandlesQuotedNameContainingComma            defect 8, uses Foo/size=1,024-4
TestParseFailsClosedOnHeaderDrift                    defect 6
TestParseFailsClosedOnUnreadableDelta                fails the parse, not just the row
TestParseFailsClosedOnMixedValidAndUnreadableRows    one clean row must not mask it
TestParseSkipsOneSidedBenchmarks                     benchstat truncates these rows
TestParseResetsUnitBetweenTables                     unit must not leak across tables
TestRegressionsIgnoreThroughputGains                 defect 4, a speedup is not a failure
TestRegressionsCatchThroughputLosses                 the inverse: -20% B/s must block
TestSelector                                         defect 3, parent/sub collapsing
TestSelectorQuotesMetacharacters                     defect 5 and regex injection
TestConfirmMatchesNameAndUnit                        defect 7
TestConfirmRequiresReproduction
TestConfirmFailsWhenCandidateWasNotRemeasured        the sibling scenario
TestConfirmAcceptsNoiseOnlyWhenCandidateWasRemeasured  the inverse, so noise still clears
TestUnmeasured                                       name/unit matching

The one-sided-benchmark handling was checked against benchstat itself rather than assumed, since making unreadable deltas fatal would otherwise hard-fail every PR that adds or removes a benchmark:

,sec/op,CI,sec/op,CI,vs base,P
Old-4,1.0000000000000002e-06,∞
Both-4,2.0000000000000003e-06,∞,2.5e-06,∞,~,p=0.333 n=2
New-4,,,1.5e-06,∞

benchstat truncates those rows to 3 and 5 fields, so they are dropped on field count and never reach the delta logic. TestParseSkipsOneSidedBenchmarks pins that shape so a future benchstat change surfaces as a test failure instead of a CI outage.

Readiness logic verified both ways:

server comes up          rc=0  proceeded to benchmarks
server never comes up    rc=1  ::error::SQL Server did not become ready; aborting benchmarks.

Also confirmed benchstat@latest installs cleanly under GOTOOLCHAIN=go1.27.0, reproduced the original failure under go1.25.7 first, and checked by parsing the YAML that both jobs now carry the log step and only one go install remains.

The OOM diagnostic is still Bash, so it is still tested by extraction from the YAML and run under -eo pipefail against a stubbed docker: an OOM kill, exit 137 without the flag, a healthy running container, a zero and a non-numeric memory limit, and docker ps / inspect / logs each failing — 11 cases, all passing, no set -e aborts. The extractor also asserts the logic is identical in both jobs. Then end-to-end against a real engine: a container held at a 16 MiB cap until the kernel killed it renders true 137 exited 16777216, confirming the field order and that .HostConfig.Memory is bytes.

actionlint is clean, go vet is clean, and golangci-lint run ./internal/benchgate/ reports 0 issues.

Timing

Worst case is a regression that reproduces: baseline pass, PR pass, then both again for confirmation. Measured on run 33399741848 — job 49m23s, baseline pass 23m57s, PR pass 23m52s — so confirmation adds about 48 minutes for a worst case near 97. timeout-minutes is 120 and the confirmation -timeout is 25m.

Corrections

Two claims I made earlier in this PR were wrong and are worth flagging for anyone reading the thread history:

  • I told copilot-pull-request-reviewer that benchmarks is not a required check, so a red result would never block a merge. It is one of the 14 required contexts on main. That premise made me under-weight the reviewer's point about untested confirmation logic, which was correct.
  • I claimed BenchmarkSelect deadlocks past one iteration. It does not — the handler loops, and it completed in a 46.1s run. Its behaviour when run alone is still unexplained, so it stays out of BENCH_PATTERN until it is understood.

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

Benchmark Results (main vs PR)

Click to expand benchstat output
goos: linux
goarch: amd64
pkg: github.com/microsoft/go-mssqldb
cpu: AMD EPYC 7763 64-Core Processor                
                         │ bench_old.txt │            bench_new.txt            │
                         │    sec/op     │    sec/op     vs base               │
TdsBuffer_Write_Small-4                12.89n ± 0%   12.88n ±  0%       ~ (p=0.265 n=10)
TdsBuffer_Write_Medium-4               21.73n ± 1%   23.86n ±  0%  +9.78% (p=0.000 n=10)
TdsBuffer_Write_Large-4                115.9n ± 0%   115.8n ±  0%       ~ (p=0.535 n=10)
TdsBuffer_WriteByte-4                  294.8n ± 0%   294.3n ±  0%       ~ (p=0.185 n=10)
TdsBuffer_Read_Small-4                 9.710n ± 2%   9.726n ±  0%       ~ (p=0.363 n=10)
TdsBuffer_ReadByte-4                   230.5n ± 0%   230.7n ±  0%       ~ (p=0.698 n=10)
TdsBuffer_Uint16-4                     126.1n ± 1%   126.0n ±  0%       ~ (p=0.146 n=10)
TdsBuffer_Uint32-4                     141.4n ± 0%   141.7n ±  0%       ~ (p=0.020 n=10)
TdsBuffer_Uint64-4                     163.9n ± 0%   163.9n ±  0%       ~ (p=0.863 n=10)
TdsBuffer_BeginFinishPacket-4          7.744n ± 0%   7.738n ±  0%       ~ (p=0.128 n=10)
ReadBVarCharOrPanicWideChars-4         170.7n ± 1%   169.3n ±  0%  -0.79% (p=0.001 n=10)
ReadBVarCharOrPanicOnly1WideChar-4     196.7n ± 0%   194.9n ±  0%  -0.92% (p=0.000 n=10)
BulkMakeParam_Int64-4                  24.62n ± 1%   24.62n ±  1%       ~ (p=0.896 n=10)
BulkMakeParam_Int32-4                  22.95n ± 1%   23.01n ±  1%       ~ (p=0.618 n=10)
BulkMakeParam_Float64-4                24.19n ± 0%   24.16n ±  0%       ~ (p=0.026 n=10)
BulkMakeParam_String_NVarChar-4        243.6n ± 1%   242.1n ±  1%       ~ (p=0.078 n=10)
BulkMakeParam_String_VarChar-4         39.97n ± 0%   39.48n ±  1%  -1.24% (p=0.003 n=10)
BulkMakeParam_DateTime-4               86.21n ± 0%   85.16n ±  1%  -1.22% (p=0.000 n=10)
BulkMakeParam_DateTime2-4              90.34n ± 0%   89.32n ±  1%       ~ (p=0.014 n=10)
BulkMakeParam_Bool-4                   23.17n ± 5%   22.84n ±  1%       ~ (p=0.381 n=10)
BulkMakeParam_MultiColumn-4            203.3n ± 1%   197.8n ±  0%  -2.73% (p=0.000 n=10)
ConvertAssign_StringToString-4         17.76n ± 2%   18.07n ±  3%       ~ (p=0.089 n=10)
ConvertAssign_StringToBytes-4          46.94n ± 1%   46.54n ±  2%       ~ (p=0.014 n=10)
ConvertAssign_BytesToString-4          62.44n ± 1%   61.79n ±  1%       ~ (p=0.050 n=10)
ConvertAssign_BytesToBytes-4           63.07n ± 1%   63.21n ±  2%       ~ (p=0.305 n=10)
ConvertAssign_Int64ToInt64-4           40.30n ± 1%   39.82n ±  0%  -1.20% (p=0.000 n=10)
ConvertAssign_Int64ToString-4          53.22n ± 0%   52.82n ±  1%  -0.74% (p=0.000 n=10)
ConvertAssign_Float64ToFloat64-4       40.52n ± 1%   40.10n ±  1%  -1.01% (p=0.001 n=10)
ConvertAssign_TimeToTime-4             43.56n ± 1%   43.35n ±  2%       ~ (p=0.631 n=10)
ConvertAssign_TimeToString-4           124.3n ± 1%   122.2n ±  1%  -1.77% (p=0.000 n=10)
ConvertAssign_NilToBytes-4             19.36n ± 1%   19.43n ±  1%       ~ (p=0.895 n=10)
ConvertAssign_Int64ToInterface-4       18.18n ± 1%   18.31n ±  3%       ~ (p=0.342 n=10)
ConvertAssign_StringToRawBytes-4       49.26n ± 1%   48.15n ±  2%       ~ (p=0.019 n=10)
ConvertAssign_BoolToBool-4             13.98n ± 5%   13.69n ±  0%  -2.11% (p=0.000 n=10)
RoundTrip_ConnectDisconnect-4          94.39m ± 1%   93.41m ±  0%  -1.04% (p=0.000 n=10)
RoundTrip_Select1-4                    365.1µ ± 3%   355.9µ ±  4%       ~ (p=0.089 n=10)
RoundTrip_ParamQuery-4                 313.2µ ± 2%   308.0µ ±  3%       ~ (p=0.353 n=10)
RoundTrip_MultiRow-4                   793.5µ ± 3%   783.6µ ±  3%       ~ (p=0.105 n=10)
RoundTrip_LargeResultSet-4             4.449m ± 1%   4.373m ±  1%  -1.71% (p=0.000 n=10)
RoundTrip_ExecInsert-4                 333.9µ ± 1%   328.8µ ±  2%  -1.52% (p=0.009 n=10)
RoundTrip_StoredProc-4                 328.9µ ± 1%   327.2µ ±  2%       ~ (p=0.218 n=10)
RoundTrip_Transaction-4                843.9µ ± 1%   846.6µ ±  1%       ~ (p=0.143 n=10)
RoundTrip_BulkInsert/Rows_100-4        3.622m ± 1%   3.611m ±  0%       ~ (p=0.353 n=10)
RoundTrip_BulkInsert/Rows_1000-4       5.907m ± 1%   5.813m ±  1%  -1.59% (p=0.000 n=10)
RoundTrip_ConcurrentQueries-4          183.5µ ± 3%   183.8µ ±  9%       ~ (p=0.853 n=10)
RoundTrip_MixedTypes-4                 458.0µ ± 2%   453.6µ ±  2%       ~ (p=0.218 n=10)
RoundTrip_LargePayload-4               597.1µ ± 1%   580.7µ ±  1%  -2.74% (p=0.000 n=10)
RoundTrip_PreparedStmt-4               313.7µ ± 3%   309.2µ ±  2%       ~ (p=0.165 n=10)
RoundTrip_MessageQuery-4               597.0µ ± 0%   598.2µ ±  0%       ~ (p=0.315 n=10)
WriteTypeInfo_Int8-4                   46.86n ± 1%   46.39n ±  0%  -1.00% (p=0.000 n=10)
WriteTypeInfo_NVarChar-4               104.5n ± 1%   103.9n ±  0%       ~ (p=0.099 n=10)
WriteTypeInfo_NVarCharMax-4            104.3n ± 1%   103.8n ±  0%  -0.57% (p=0.000 n=10)
WriteByteLenType-4                     30.23n ± 1%   29.73n ±  0%  -1.64% (p=0.000 n=10)
WriteShortLenType-4                    35.30n ± 3%   34.88n ±  0%  -1.19% (p=0.000 n=10)
WritePLPType_Short-4                   85.20n ± 0%   83.88n ±  0%  -1.55% (p=0.000 n=10)
SendRpc_SingleIntParam-4               12.58µ ± 1%   12.74µ ±  3%       ~ (p=0.149 n=10)
Str2ucs2_Short-4                       81.71n ± 1%   79.98n ±  1%  -2.12% (p=0.000 n=10)
Str2ucs2_Medium-4                      176.2n ± 1%   170.9n ±  1%  -3.01% (p=0.000 n=10)
Str2ucs2_Long-4                        637.3n ± 0%   620.1n ±  0%  -2.71% (p=0.000 n=10)
Ucs22str_ASCII-4                       56.73n ± 1%   55.69n ± 10%       ~ (p=0.022 n=10)
Ucs22str_Unicode-4                     216.8n ± 2%   214.8n ±  0%  -0.90% (p=0.000 n=10)
Ucs22str_LongASCII-4                   114.1n ± 1%   111.3n ±  1%  -2.45% (p=0.001 n=10)
ManglePassword_Short-4                 98.02n ± 1%   96.02n ±  1%  -2.04% (p=0.000 n=10)
ManglePassword_Long-4                  379.9n ± 0%   372.0n ±  1%  -2.07% (p=0.000 n=10)
SendLogin-4                            1.362µ ± 2%   1.332µ ±  1%  -2.20% (p=0.000 n=10)
WritePrelogin-4                        618.1n ± 1%   609.3n ±  0%  -1.44% (p=0.000 n=10)
Ucs22strAscii-4                        14.02n ± 0%   13.75n ±  1%  -1.96% (p=0.000 n=10)
Ucs22strMediumAscii-4                  23.45n ± 0%   23.07n ±  1%  -1.66% (p=0.002 n=10)
Ucs22strLongAscii-4                    52.75n ± 0%   51.26n ±  0%  -2.83% (p=0.000 n=10)
Ucs22strLongerAscii-4                  394.6n ± 0%   379.5n ±  1%  -3.84% (p=0.000 n=10)
Ucs22strTrailingUnicode-4              151.0n ± 0%   147.1n ±  0%  -2.58% (p=0.001 n=10)
Ucs22strLongEmojis-4                   229.8n ± 1%   225.2n ±  0%  -2.04% (p=0.000 n=10)
ParseDone-4                            9.454n ± 0%   9.432n ±  0%       ~ (p=0.165 n=10)
ParseDoneInProc-4                      9.454n ± 0%   9.433n ±  0%       ~ (p=0.093 n=10)
ParseReturnStatus-4                    2.506n ± 0%   2.508n ±  1%       ~ (p=0.289 n=10)
ParseOrder-4                           25.81n ± 0%   25.09n ±  1%  -2.77% (p=0.000 n=10)
ParseError72-4                         189.1n ± 1%   183.7n ±  1%  -2.88% (p=0.000 n=10)
ParseInfo-4                            197.8n ± 1%   192.5n ±  1%  -2.65% (p=0.000 n=10)
ParseLoginAck-4                        71.16n ± 1%   68.58n ±  2%  -3.63% (p=0.000 n=10)
ParseFeatureExtAck_Empty-4             35.07n ± 1%   33.65n ±  1%  -4.06% (p=0.000 n=10)
ParseTabName-4                         53.35n ± 1%   51.86n ±  1%  -2.80% (p=0.000 n=10)
ParseColInfo-4                         52.93n ± 1%   51.77n ±  1%  -2.19% (p=0.000 n=10)
ReadFixedType_Int64-4                  27.68n ± 1%   27.01n ±  1%  -2.44% (p=0.000 n=10)
ReadFixedType_Int32-4                  16.61n ± 0%   16.58n ±  0%       ~ (p=0.170 n=10)
ReadFixedType_Float64-4                26.89n ± 0%   26.35n ±  1%  -2.01% (p=0.000 n=10)
ReadFixedType_DateTime-4               55.88n ± 1%   54.66n ±  1%  -2.17% (p=0.000 n=10)
ReadByteLenType_IntN_8-4               28.28n ± 0%   27.76n ±  0%  -1.84% (p=0.000 n=10)
ReadByteLenType_IntN_4-4               28.62n ± 1%   28.05n ±  1%  -1.99% (p=0.001 n=10)
ReadByteLenType_FloatN_8-4             28.30n ± 0%   27.71n ±  1%  -2.12% (p=0.000 n=10)
ReadByteLenType_BitN-4                 16.61n ± 0%   16.64n ±  2%       ~ (p=0.136 n=10)
ReadByteLenType_Null-4                 5.957n ± 0%   5.955n ±  0%       ~ (p=0.698 n=10)
ReadShortLenType_NVarChar_Short-4      51.99n ± 0%   50.55n ±  1%  -2.78% (p=0.000 n=10)
ReadShortLenType_NVarChar_Medium-4     89.36n ± 0%   86.52n ±  1%  -3.17% (p=0.000 n=10)
ReadShortLenType_VarBinary-4           65.31n ± 1%   62.67n ±  2%  -4.06% (p=0.000 n=10)
ReadByteLenType_VarChar-4              62.59n ± 1%   60.70n ±  1%  -3.02% (p=0.000 n=10)
DecodeDateTime-4                       39.85n ± 0%   38.64n ±  1%  -3.05% (p=0.000 n=10)
DecodeDateTim4-4                       37.32n ± 0%   36.30n ±  1%  -2.75% (p=0.000 n=10)
EncodeDateTime-4                       75.14n ± 0%   74.08n ±  1%  -1.42% (p=0.000 n=10)
geomean                                266.9n        263.6n        -1.24%

               │ bench_old.txt │            bench_new.txt            │
               │      B/s      │     B/s       vs base               │
TdsBuffer_Write_Small-4     4.625Gi ± 0%   4.629Gi ± 0%       ~ (p=0.353 n=10)
TdsBuffer_Write_Medium-4    43.87Gi ± 1%   39.97Gi ± 0%  -8.89% (p=0.000 n=10)
TdsBuffer_Write_Large-4     65.80Gi ± 0%   65.85Gi ± 0%       ~ (p=0.739 n=10)
TdsBuffer_Read_Small-4      6.139Gi ± 1%   6.128Gi ± 0%       ~ (p=0.353 n=10)
Str2ucs2_Short-4            70.03Mi ± 1%   71.54Mi ± 1%  +2.17% (p=0.000 n=10)
Str2ucs2_Medium-4           146.1Mi ± 1%   150.7Mi ± 1%  +3.14% (p=0.000 n=10)
Str2ucs2_Long-4             149.6Mi ± 0%   153.8Mi ± 0%  +2.79% (p=0.000 n=10)
Ucs22str_ASCII-4            1.182Gi ± 1%   1.204Gi ± 9%       ~ (p=0.023 n=10)
Ucs22str_Unicode-4          211.1Mi ± 2%   213.1Mi ± 0%  +0.93% (p=0.000 n=10)
Ucs22str_LongASCII-4        2.073Gi ± 1%   2.126Gi ± 1%  +2.53% (p=0.001 n=10)
geomean                     1.504Gi        1.509Gi       +0.39%

                         │ bench_old.txt  │             bench_new.txt             │
                         │      B/op      │     B/op      vs base                 │
TdsBuffer_Write_Small-4                0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Medium-4               0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Large-4                0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_WriteByte-4                  0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Read_Small-4                 0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_ReadByte-4                   0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint16-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint32-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint64-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_BeginFinishPacket-4          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicWideChars-4         72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicOnly1WideChar-4     97.00 ± 0%       97.00 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int64-4                  8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int32-4                  4.000 ± 0%       4.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Float64-4                8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_NVarChar-4        304.0 ± 0%       304.0 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_VarChar-4         48.00 ± 0%       48.00 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime-4               32.00 ± 0%       32.00 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime2-4              32.00 ± 0%       32.00 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Bool-4                   1.000 ± 0%       1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_MultiColumn-4            72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToString-4         16.00 ± 0%       16.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToBytes-4          72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToString-4          88.00 ± 0%       88.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToBytes-4           96.00 ± 0%       96.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInt64-4           8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToString-4          32.00 ± 0%       32.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Float64ToFloat64-4       8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToTime-4             48.00 ± 0%       48.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToString-4           72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_NilToBytes-4             24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInterface-4       16.00 ± 0%       16.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToRawBytes-4       72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BoolToBool-4             1.000 ± 0%       1.000 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ConnectDisconnect-4        163.9Ki ± 3%     164.4Ki ± 3%       ~ (p=0.529 n=10)
RoundTrip_Select1-4                  2.602Ki ± 0%     2.602Ki ± 0%       ~ (p=1.000 n=10)
RoundTrip_ParamQuery-4               5.782Ki ± 0%     5.782Ki ± 0%       ~ (p=1.000 n=10)
RoundTrip_MultiRow-4                 37.16Ki ± 0%     37.16Ki ± 0%       ~ (p=0.305 n=10)
RoundTrip_LargeResultSet-4           277.3Ki ± 0%     277.3Ki ± 0%       ~ (p=0.134 n=10)
RoundTrip_ExecInsert-4               6.109Ki ± 0%     6.109Ki ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_StoredProc-4               5.399Ki ± 0%     5.399Ki ± 0%       ~ (p=1.000 n=10)
RoundTrip_Transaction-4              7.441Ki ± 0%     7.441Ki ± 0%       ~ (p=0.953 n=10)
RoundTrip_BulkInsert/Rows_100-4      72.39Ki ± 0%     72.37Ki ± 0%       ~ (p=0.529 n=10)
RoundTrip_BulkInsert/Rows_1000-4     583.8Ki ± 0%     583.8Ki ± 0%       ~ (p=0.481 n=10)
RoundTrip_ConcurrentQueries-4        2.680Ki ± 0%     2.683Ki ± 0%       ~ (p=0.305 n=10)
RoundTrip_MixedTypes-4               6.643Ki ± 0%     6.642Ki ± 0%       ~ (p=1.000 n=10)
RoundTrip_LargePayload-4             134.7Ki ± 0%     134.7Ki ± 0%       ~ (p=0.984 n=10)
RoundTrip_PreparedStmt-4             5.454Ki ± 0%     5.454Ki ± 0%       ~ (p=1.000 n=10)
RoundTrip_MessageQuery-4             13.76Ki ± 0%     13.76Ki ± 0%       ~ (p=0.552 n=10)
WriteTypeInfo_Int8-4                   2.000 ± 0%       2.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarChar-4               8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarCharMax-4            8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteByteLenType-4                     1.000 ± 0%       1.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteShortLenType-4                    2.000 ± 0%       2.000 ± 0%       ~ (p=1.000 n=10) ¹
WritePLPType_Short-4                   16.00 ± 0%       16.00 ± 0%       ~ (p=1.000 n=10) ¹
SendRpc_SingleIntParam-4             64.21Ki ± 0%     64.21Ki ± 0%       ~ (p=1.000 n=10)
Str2ucs2_Short-4                       56.00 ± 0%       56.00 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Medium-4                      152.0 ± 0%       152.0 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Long-4                        856.0 ± 0%       856.0 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_ASCII-4                       64.00 ± 0%       64.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_Unicode-4                     72.00 ± 0%       72.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_LongASCII-4                   144.0 ± 0%       144.0 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Short-4                 56.00 ± 0%       56.00 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Long-4                  408.0 ± 0%       408.0 ± 0%       ~ (p=1.000 n=10) ¹
SendLogin-4                            448.0 ± 0%       448.0 ± 0%       ~ (p=1.000 n=10) ¹
WritePrelogin-4                        50.00 ± 0%       50.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strAscii-4                        3.000 ± 0%       3.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strMediumAscii-4                  16.00 ± 0%       16.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongAscii-4                    64.00 ± 0%       64.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongerAscii-4                  640.0 ± 0%       640.0 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strTrailingUnicode-4              48.00 ± 0%       48.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongEmojis-4                   128.0 ± 0%       128.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseDone-4                            0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseDoneInProc-4                      0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseReturnStatus-4                    0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseOrder-4                           8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError72-4                         128.0 ± 0%       128.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseInfo-4                            160.0 ± 0%       160.0 ± 0%       ~ (p=1.000 n=10) ¹
ParseLoginAck-4                        88.00 ± 0%       88.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseFeatureExtAck_Empty-4             48.00 ± 0%       48.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseTabName-4                         24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
ParseColInfo-4                         24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int64-4                  8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int32-4                  0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Float64-4                8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_DateTime-4               24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_8-4               8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_4-4               8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_FloatN_8-4             8.000 ± 0%       8.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_BitN-4                 0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_Null-4                 0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Short-4      21.00 ± 0%       21.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Medium-4     80.00 ± 0%       80.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_VarBinary-4           88.00 ± 0%       88.00 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_VarChar-4              32.00 ± 0%       32.00 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTime-4                       24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTim4-4                       24.00 ± 0%       24.00 ± 0%       ~ (p=1.000 n=10) ¹
EncodeDateTime-4                       32.00 ± 0%       32.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                           ²                 +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                         │ bench_old.txt │            bench_new.txt             │
                         │   allocs/op   │  allocs/op   vs base                 │
TdsBuffer_Write_Small-4               0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Medium-4              0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Large-4               0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_WriteByte-4                 0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Read_Small-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_ReadByte-4                  0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint16-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint32-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint64-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_BeginFinishPacket-4         0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicWideChars-4        4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicOnly1WideChar-4    4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int64-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int32-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Float64-4               1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_NVarChar-4       3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_VarChar-4        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime-4              2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime2-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Bool-4                  1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_MultiColumn-4           5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToString-4        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToBytes-4         2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToString-4         3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToBytes-4          3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInt64-4          1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToString-4         2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Float64ToFloat64-4      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToTime-4            2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToString-4          3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_NilToBytes-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInterface-4      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToRawBytes-4      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BoolToBool-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ConnectDisconnect-4         748.0 ± 0%      749.5 ± 0%       ~ (p=0.155 n=10)
RoundTrip_Select1-4                   47.00 ± 0%      47.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ParamQuery-4                116.0 ± 0%      116.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MultiRow-4                 1.354k ± 0%     1.354k ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_LargeResultSet-4           10.05k ± 0%     10.05k ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ExecInsert-4                102.0 ± 0%      102.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_StoredProc-4                94.00 ± 0%      94.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_Transaction-4               138.0 ± 0%      138.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_BulkInsert/Rows_100-4      2.110k ± 0%     2.110k ± 0%       ~ (p=1.000 n=10)
RoundTrip_BulkInsert/Rows_1000-4     21.45k ± 0%     21.45k ± 0%       ~ (p=1.000 n=10)
RoundTrip_ConcurrentQueries-4         47.00 ± 0%      47.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MixedTypes-4                72.00 ± 0%      72.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_LargePayload-4              110.0 ± 0%      110.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_PreparedStmt-4              103.0 ± 0%      103.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MessageQuery-4              215.0 ± 0%      215.0 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_Int8-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarChar-4              4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarCharMax-4           4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteByteLenType-4                    1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteShortLenType-4                   1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
WritePLPType_Short-4                  3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
SendRpc_SingleIntParam-4              17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Short-4                      3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Medium-4                     3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Long-4                       4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_ASCII-4                      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_Unicode-4                    3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_LongASCII-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Short-4                3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Long-4                 4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
SendLogin-4                           15.00 ± 0%      15.00 ± 0%       ~ (p=1.000 n=10) ¹
WritePrelogin-4                       12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strAscii-4                       1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strMediumAscii-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongAscii-4                   1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongerAscii-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strTrailingUnicode-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongEmojis-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseDone-4                           0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseDoneInProc-4                     0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseReturnStatus-4                   0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseOrder-4                          1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError72-4                        7.000 ± 0%      7.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseInfo-4                           7.000 ± 0%      7.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseLoginAck-4                       2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseFeatureExtAck_Empty-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseTabName-4                        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseColInfo-4                        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int64-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int32-4                 0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Float64-4               1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_DateTime-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_8-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_4-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_FloatN_8-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_BitN-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_Null-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Short-4     2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Medium-4    2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_VarBinary-4          2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_VarChar-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTime-4                      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTim4-4                      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
EncodeDateTime-4                      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                          ²                +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/microsoft/go-mssqldb/msdsn
             │ bench_old.txt │           bench_new.txt            │
             │    sec/op     │   sec/op     vs base               │
Parse_URL-4                3.576µ ± 2%   3.600µ ± 1%       ~ (p=0.305 n=10)
Parse_URL_Azure-4          4.259µ ± 1%   4.300µ ± 0%  +0.96% (p=0.001 n=10)
Parse_ADO-4                4.232µ ± 1%   4.253µ ± 1%       ~ (p=0.159 n=10)
Parse_URL_Minimal-4        2.343µ ± 1%   2.347µ ± 1%       ~ (p=0.781 n=10)
Parse_URL_ManyParams-4     6.740µ ± 0%   6.708µ ± 1%       ~ (p=0.289 n=10)
geomean                    3.995µ        4.010µ       +0.36%

             │ bench_old.txt │             bench_new.txt             │
             │     B/op      │     B/op      vs base                 │
Parse_URL-4               2.047Ki ± 0%   2.047Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Azure-4         2.680Ki ± 0%   2.680Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_ADO-4               2.500Ki ± 0%   2.500Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Minimal-4       1.625Ki ± 0%   1.625Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_ManyParams-4    4.695Ki ± 0%   4.695Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean                   2.535Ki        2.535Ki       +0.00%
¹ all samples are equal

             │ bench_old.txt │            bench_new.txt            │
             │   allocs/op   │ allocs/op   vs base                 │
Parse_URL-4                 22.00 ± 0%   22.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Azure-4           26.00 ± 0%   26.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_ADO-4                 40.00 ± 0%   40.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Minimal-4         16.00 ± 0%   16.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_ManyParams-4      41.00 ± 0%   41.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                     27.24        27.24       +0.00%
¹ all samples are equal

Generated by CI — commit 5d3839c

@dlevy-msft-sql David Levy (dlevy-msft-sql) changed the title ci: pin benchstat to the last Go 1.25 compatible version ci: unblock and harden the benchmark job Aug 21, 2026
@dlevy-msft-sql
David Levy (dlevy-msft-sql) marked this pull request as ready for review August 21, 2026 23:42
Copilot AI lite review requested due to automatic review settings August 21, 2026 23:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR unblocks the benchmarks CI job and makes its regression gate more reliable by pinning the benchstat tool version and hardening the parsing/validation logic around benchstat output.

Changes:

  • Pin golang.org/x/perf/cmd/benchstat to a pre-Go-1.26 commit to prevent toolchain breakage and avoid silent output-format drift.
  • Make the regression gate fail closed when benchstat output is empty or unrecognized, instead of reporting success.
  • Fix regression/improvement detection to handle integer percentages and avoid set -eo pipefail aborts when no matches occur.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/pr-validation.yml Outdated
Copilot AI review requested due to automatic review settings August 22, 2026 16:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/pr-validation.yml:210

  • grep -E uses POSIX ERE; \S / \s are not defined there (they’re PCRE). On some runners this will be treated as literal S/s, making the row-detection regex fail and the gate behave incorrectly. Use POSIX whitespace classes instead.

This issue also appears in the following locations of the same file:

  • line 221
  • line 234
          if ! ROWS=$(grep_ok -cE '^\S+\s+.+(±|~|[+-][0-9])' bench_diff.txt); then

.github/workflows/pr-validation.yml:221

  • This regex also uses \S/\s with grep -E, which is not portable ERE and can silently stop matching. Switch to POSIX classes ([^[:space:]] / [[:space:]]) so the improvement notice doesn’t depend on PCRE-only escapes.
          if grep_ok -v '~' bench_diff.txt | grep -E '^\S+\s+.+\s+-[0-9]+(\.[0-9]+)?%'; then

.github/workflows/pr-validation.yml:234

  • The regression-detection grep_ok -E pattern uses \S/\s (PCRE-only) with grep -E. If the pattern fails to match due to ERE semantics, regressions can be missed or the pipeline can behave unexpectedly. Use POSIX whitespace classes instead.
                | grep_ok -E '^\S+\s+.+\s+\+[0-9]+(\.[0-9]+)?%' \

Copilot AI review requested due to automatic review settings August 22, 2026 16:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/pr-validation.yml
Copilot AI review requested due to automatic review settings August 22, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

.github/workflows/pr-validation.yml:122

  • The PR description says benchstat should be pinned (to avoid CI breakage when parsing its output). This step still installs benchstat@latest, so the job can break again when golang.org/x/perf changes its Go version floor or output format.
      - name: Install benchstat
        shell: bash
        # Before the benchmark runs, not after: they take ~46 minutes and this
        # reaches outside the repo, so a fetch failure should surface at once.
        run: |
          go install golang.org/x/perf/cmd/benchstat@latest
          command -v benchstat

Comment thread .github/workflows/pr-validation.yml Outdated
Copilot AI review requested due to automatic review settings August 22, 2026 17:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

.github/workflows/pr-validation.yml:239

  • This grep -E pattern uses GNU-only \S/\s escapes. Switching to POSIX character classes ([^[:space:]] / [[:space:]]) makes it robust across regex implementations without changing intent.
          if grep_ok -v '~' bench_diff.txt | grep -E '^\S+\s+.+\s+-[0-9]+(\.[0-9]+)?%'; then

.github/workflows/pr-validation.yml:252

  • This grep -E pattern uses GNU-only \S/\s escapes. Prefer POSIX character classes so the regression gate’s parsing doesn’t depend on GNU grep extensions.
                | grep_ok -E '^\S+\s+.+\s+\+[0-9]+(\.[0-9]+)?%' \

Comment thread .github/workflows/pr-validation.yml
Comment thread .github/workflows/pr-validation.yml Outdated
@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Aug 22, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.13%. Comparing base (0ad75cd) to head (ae36335).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #437      +/-   ##
==========================================
- Coverage   82.17%   82.13%   -0.05%     
==========================================
  Files          35       35              
  Lines        7065     7065              
==========================================
- Hits         5806     5803       -3     
- Misses        993      995       +2     
- Partials      266      267       +1     

see 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It substantially changes a required CI gate, and the latest long-running validation checks remain in progress.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It substantially rewrites a required CI gate, and its latest long-running validation checks remain incomplete.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It substantially changes a required merge gate, and the latest full CI and benchmark runs remain incomplete.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It modifies a required regression gate whose end-to-end benchmark and Windows checks remain pending.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It substantially changes a required CI gate, and the latest benchmark validation is still running.

Review effort: Balanced
Findings: 1 Low severity

Open (1)

Comment thread .github/workflows/pr-validation.yml Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The parser can still accept shifted CSV columns and silently miss a regression.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread internal/benchgate/benchgate.go Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The parser rejects benchstat’s actual filename header, causing the required benchmark check to fail.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread internal/benchgate/benchgate.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The parser rejects benchstat’s real filename-based CSV header, causing the required benchmark check to fail.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The parser can retain a stale unit across table preambles and incorrectly clear a regression.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread internal/benchgate/benchgate.go Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Short malformed benchmark rows can still be silently discarded, allowing the regression gate to report success on unrecognized output.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread internal/benchgate/benchgate.go
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It substantially changes a required CI gate, and its latest end-to-end benchmark run remains in progress.

Review effort: Balanced
Findings: None

Resolved since last review (1)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It substantially rewrites a required CI gate, and the latest benchmark and matrix checks are still running.

Review effort: Balanced
Findings: None

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It changes a required performance gate, and its end-to-end benchmark check was still running during review.

Review effort: Balanced
Findings: None

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Confirmation can accept a one-sided row as a valid remeasurement and incorrectly clear a regression.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment thread internal/benchgate/benchgate.go Outdated
Parse validated measurement shape on the truncated-row and blank-delta
paths but not on a full-width row with a nonblank delta. Such a row became
an insignificant Row, so Unmeasured counted the key as compared and a
flagged benchmark that never re-ran cleared the gate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6388e79-ee7f-4b63-8242-9a01d2ea7fde

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The required benchmark check is still running, so the new gate has not yet been validated end to end.

Review effort: Balanced
Findings: None

Resolved since last review (1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants