From 92decdcbaaef89e93163bb54885aa52fb5a13881 Mon Sep 17 00:00:00 2001 From: Julien Cretel Date: Mon, 27 Oct 2025 12:40:37 +0000 Subject: [PATCH] net/url: further speed up escape and unescape MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase --- src/net/url/url.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/net/url/url.go b/src/net/url/url.go index 71fd8f59b3..6d1d505343 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -56,17 +56,9 @@ func ishex(c byte) bool { 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 @@ -161,19 +153,24 @@ func unescape(s string, mode encoding) (string, error) { 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]) } @@ -195,8 +192,7 @@ func PathEscape(s string) string { 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++ @@ -231,8 +227,8 @@ func escape(s string, mode encoding) string { } 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++ @@ -242,7 +238,7 @@ func escape(s string, mode encoding) string { t[j+2] = upperhex[c&15] j += 3 default: - t[j] = s[i] + t[j] = c j++ } } -- 2.52.0