]> Cypherpunks repositories - gostls13.git/commitdiff
net/url: further speed up escape and unescape
authorJulien Cretel <jub0bsinthecloud@gmail.com>
Mon, 27 Oct 2025 12:40:37 +0000 (12:40 +0000)
committert hepudds <thepudds1460@gmail.com>
Mon, 27 Oct 2025 16:51:24 +0000 (09:51 -0700)
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>
src/net/url/url.go

index 71fd8f59b3f45111cb5b756d71d95b97e2e5c9e6..6d1d50534355536c2d82814027f635fcae940e74 100644 (file)
@@ -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++
                }
        }