Skip to content

perf(context): skip query segments that cannot match in QueryParam - #3215

Open
g4lb wants to merge 1 commit into
labstack:masterfrom
g4lb:perf-query-param-scan
Open

g4lb wants to merge 1 commit into
labstack:masterfrom
g4lb:perf-query-param-scan

Conversation

@g4lb

@g4lb g4lb commented Nov 10, 2026

Copy link
Copy Markdown

What

getRawQueryParam unescaped the key of each segment before it compared the key with the name. Now it skips a segment when the raw key is shorter than the name, because unescaping never makes a key longer. It also splits segments with strings.IndexByte. It checks for an empty segment or a ; only after the key matches.

The result does not change. TestGetRawQueryParamMatchesStdlib gets two cases for the length check: an escaped key that matches, and keys that are longer or shorter than the name.

Numbers

amd64 (Intel Xeon Platinum 8573C, Go 1.27.3), 24 interleaved rounds, benchstat:

Query Before After Change
QueryParam_FastPath 68.8 ns 41.7 ns -39.4%
c.QueryParam("page"), 4 pairs 126.0 ns 79.2 ns -37.1%
6 utm-style pairs, last key 227.7 ns 150.0 ns -34.1%
6 utm-style pairs, key not present 221.3 ns 147.1 ns -33.5%
4 pairs, key not present 110.8 ns 31.1 ns -71.9%
51 pairs, last key 1074 ns 572 ns -46.7%
escaped key na%6De 68.9 ns 64.9 ns -5.7%
500-byte value 708.6 ns 699.2 ns -1.3%

Allocations do not change.

I measured 36 benchmarks in three code layouts, with getRawQueryParam moved by 0, 9 and 19 bytes. 21 query rows were faster in all three layouts. No row was slower in all three layouts. In layout 0, five ServeHTTP rows were 6% to 16% slower. In the other two layouts, the same rows were 5% to 15% faster. These benchmarks do not call getRawQueryParam. On Apple M5 (Go 1.26.5, 20 rounds), no row was slower.

Checks:

  • go test -race ./... passes. Go 1.25.0 also passes.
  • staticcheck ./... and golint ./... show no issues.
  • Old and new results of QueryParam, QueryParams, Bind and routing matched on 1.1M random inputs.

getRawQueryParam now skips a segment whose raw key is shorter than the
name before it unescapes the key, because unescaping never makes a key
longer. It also splits segments with strings.IndexByte, and checks for
an empty segment or a ';' only after the key matches. The result does
not change.
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.

1 participant