This change is a follow-up to CL 712200. It further simplifies and speeds up
functions escape and unescape.
Here are some benchmark results (no change to allocations):
goos: darwin
goarch: amd64
pkg: net/url
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
│ go/src/old │ go/src/new │
│ sec/op │ sec/op vs base │
QueryEscape/#00-8 34.58n ± 1% 31.97n ± 1% -7.55% (p=0.000 n=20)
QueryEscape/#01-8 92.92n ± 0% 94.63n ± 0% +1.84% (p=0.000 n=20)
QueryEscape/#02-8 75.44n ± 0% 73.32n ± 0% -2.80% (p=0.000 n=20)
QueryEscape/#03-8 143.4n ± 0% 136.6n ± 0% -4.71% (p=0.000 n=20)
QueryEscape/#04-8 918.8n ± 1% 838.3n ± 0% -8.76% (p=0.000 n=20)
PathEscape/#00-8 43.93n ± 0% 42.86n ± 0% -2.44% (p=0.000 n=20)
PathEscape/#01-8 94.99n ± 0% 95.86n ± 0% +0.91% (p=0.000 n=20)
PathEscape/#02-8 75.40n ± 1% 71.50n ± 1% -5.18% (p=0.000 n=20)
PathEscape/#03-8 143.4n ± 0% 136.2n ± 0% -4.99% (p=0.000 n=20)
PathEscape/#04-8 871.8n ± 0% 822.7n ± 0% -5.63% (p=0.000 n=20)
QueryUnescape/#00-8 52.64n ± 1% 51.19n ± 0% -2.75% (p=0.000 n=20)
QueryUnescape/#01-8 137.4n ± 1% 137.9n ± 1% ~ (p=0.297 n=20)
QueryUnescape/#02-8 114.0n ± 0% 122.3n ± 1% +7.24% (p=0.000 n=20)
QueryUnescape/#03-8 271.8n ± 0% 260.7n ± 1% -4.08% (p=0.000 n=20)
QueryUnescape/#04-8 1.390µ ± 1% 1.355µ ± 0% -2.52% (p=0.000 n=20)
PathUnescape/#00-8 52.45n ± 1% 53.03n ± 1% +1.10% (p=0.008 n=20)
PathUnescape/#01-8 138.5n ± 1% 141.3n ± 0% +2.06% (p=0.000 n=20)
PathUnescape/#02-8 114.0n ± 0% 121.5n ± 0% +6.62% (p=0.000 n=20)
PathUnescape/#03-8 273.1n ± 1% 260.1n ± 0% -4.76% (p=0.000 n=20)
PathUnescape/#04-8 1.431µ ± 1% 1.359µ ± 0% -5.07% (p=0.000 n=20)
geomean 160.4n 156.9n -2.14%
Updates #17860
Change-Id: If64ac3e9c62c41f672db06cfd7eab7357e934e6d
GitHub-Last-Rev:
1da047ac75f9a710baf75a45d105db4dc7b81810
GitHub-Pull-Request: golang/go#76048
Reviewed-on: https://go-review.googlesource.com/c/go/+/714900
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
return table[c]&hexChar != 0
}
+// Precondition: ishex(c) is true.
func unhex(c byte) byte {
- switch {
- case '0' <= c && c <= '9':
- return c - '0'
- case 'a' <= c && c <= 'f':
- return c - 'a' + 10
- case 'A' <= c && c <= 'F':
- return c - 'A' + 10
- default:
- panic("invalid hex character")
- }
+ return 9*(c>>6) + (c & 15)
}
type EscapeError string
return s, nil
}
+ var unescapedPlusSign byte
+ switch mode {
+ case encodeQueryComponent:
+ unescapedPlusSign = ' '
+ default:
+ unescapedPlusSign = '+'
+ }
var t strings.Builder
t.Grow(len(s) - 2*n)
for i := 0; i < len(s); i++ {
switch s[i] {
case '%':
+ // In the loop above, we established that unhex's precondition is
+ // fulfilled for both s[i+1] and s[i+2].
t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
i += 2
case '+':
- if mode == encodeQueryComponent {
- t.WriteByte(' ')
- } else {
- t.WriteByte('+')
- }
+ t.WriteByte(unescapedPlusSign)
default:
t.WriteByte(s[i])
}
func escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
- for i := 0; i < len(s); i++ {
- c := s[i]
+ for _, c := range []byte(s) {
if shouldEscape(c, mode) {
if c == ' ' && mode == encodeQueryComponent {
spaceCount++
}
j := 0
- for i := 0; i < len(s); i++ {
- switch c := s[i]; {
+ for _, c := range []byte(s) {
+ switch {
case c == ' ' && mode == encodeQueryComponent:
t[j] = '+'
j++
t[j+2] = upperhex[c&15]
j += 3
default:
- t[j] = s[i]
+ t[j] = c
j++
}
}