KEMBAR78
x/net/http/httpguts: validation of header names, methods, and cookie names could be about twice as fast · Issue #66700 · golang/go · GitHub
Skip to content

x/net/http/httpguts: validation of header names, methods, and cookie names could be about twice as fast #66700

@jub0bs

Description

@jub0bs

Go version

go1.22.2

golang/x/net v0.24.0

Output of go env in your module/workspace:

GOARCH='amd64'
GOOS='darwin'

What did you do?

I noticed that httpguts.IsTokenRune, which httpguts.ValidHeaderFieldName calls iteratively and which net/http in general relies on, systematically incurs a bounds check. I then wrote an alternative implementation that eliminates those bounds checks and also obviates the need for UTF-8 decoding.

package p

import "unicode/utf8"

func ValidHeaderFieldName(v string) bool {
	return isToken(v)
}

func isToken(s string) bool {
	if len(s) == 0 {
		return false
	}
	for i := 0; i < len(s); i++ {
		if !validTokenByte[s[i]] {
			return false
		}
	}
	return true
}

func IsTokenRune(r rune) bool {
	return r < utf8.SelfRune && validTokenByte[byte(r)]
}

var validTokenByte = [256]bool{
	'!':  true,
	'#':  true,
	'$':  true,
	'%':  true,
	'&':  true,
	'\'': true,
	'*':  true,
	'+':  true,
	'-':  true,
	'.':  true,
	'0':  true,
	'1':  true,
	// rest omitted for brevity
	'~':  true,
}

What did you see happen?

Benchmarks indicate that my implementation is about twice as fast as the current one; see below.

Moreover, the same logic could be used to validate HTTP methods and cookie names in addition to HTTP header-field names, since all three share the same production (token). At the moment, their validation logic, because it relies on strings.IndexFunc and httpguts.IsTokenRune, isn't as fast as it could, as shown by one of my benchmarks.

/cc @bradfitz @neild

goos: darwin
goarch: amd64
pkg: github.com/jub0bs/httpguts-perf-exp
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
                       │     std      │               jub0bs                │
                       │    sec/op    │   sec/op     vs base                │
IsCookieNameValid-8      1615.0n ± 0%   363.8n ± 1%  -77.48% (p=0.000 n=20)
ValidHeaderFieldName-8    669.3n ± 1%   328.1n ± 0%  -50.99% (p=0.000 n=20)
IsTokenRune-8             594.8n ± 0%   592.2n ± 0%   -0.45% (p=0.004 n=20)
geomean                   863.1n        413.4n       -52.10%

                       │     std      │               jub0bs                │
                       │     B/op     │    B/op     vs base                 │
IsCookieNameValid-8      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
ValidHeaderFieldName-8   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
IsTokenRune-8            0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                             ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                       │     std      │               jub0bs                │
                       │  allocs/op   │ allocs/op   vs base                 │
IsCookieNameValid-8      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
ValidHeaderFieldName-8   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
IsTokenRune-8            0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                             ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

What did you expect to see?

I expected at least some speedup of httpguts.ValidHeaderFieldName.

I have not yet checked how such a change would affect the performance of net/http, though.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions