]> Cypherpunks repositories - gostls13.git/commitdiff
net/url: reduce allocs in Encode
authorJulien Cretel <jub0bsinthecloud@gmail.com>
Sat, 18 Oct 2025 19:33:12 +0000 (19:33 +0000)
committert hepudds <thepudds1460@gmail.com>
Mon, 20 Oct 2025 21:16:39 +0000 (14:16 -0700)
This change adds benchmarks for Encode and reverts what CL 617356 did in
this package. At the moment, using maps.Keys in conjunction with
slices.Sorted indeed causes a bunch of closures to escape to heap.
Moreover, all other things being equal, pre-sizing the slice in which
we collect the keys is beneficial to performance when they are "many" (>8)
keys because it results in fewer allocations than if we don't pre-size the
slice.

Here are some benchmark results:

goos: darwin
goarch: amd64
pkg: net/url
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
                                                           │     old      │                 new                 │
                                                           │    sec/op    │   sec/op     vs base                │
EncodeQuery/#00-8                                             2.051n ± 1%   2.343n ± 1%  +14.24% (p=0.000 n=20)
EncodeQuery/#01-8                                             2.337n ± 1%   2.458n ± 4%   +5.16% (p=0.000 n=20)
EncodeQuery/oe=utf8&q=puppies-8                               489.6n ± 0%   284.5n ± 0%  -41.88% (p=0.000 n=20)
EncodeQuery/q=dogs&q=%26&q=7-8                                397.2n ± 1%   231.7n ± 1%  -41.66% (p=0.000 n=20)
EncodeQuery/a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3-8    743.1n ± 0%   519.0n ± 0%  -30.16% (p=0.000 n=20)
EncodeQuery/a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i-8            1324.0n ± 0%   931.0n ± 0%  -29.68% (p=0.000 n=20)
geomean                                                       98.57n        75.38n       -23.53%

                                                           │      old      │                 new                  │
                                                           │     B/op      │    B/op     vs base                  │
EncodeQuery/#00-8                                             0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=20) ¹
EncodeQuery/#01-8                                             0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=20) ¹
EncodeQuery/oe=utf8&q=puppies-8                              168.00 ± 0%     56.00 ± 0%  -66.67% (p=0.000 n=20)
EncodeQuery/q=dogs&q=%26&q=7-8                               112.00 ± 0%     32.00 ± 0%  -71.43% (p=0.000 n=20)
EncodeQuery/a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3-8    296.0 ± 0%     168.0 ± 0%  -43.24% (p=0.000 n=20)
EncodeQuery/a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i-8             680.0 ± 0%     264.0 ± 0%  -61.18% (p=0.000 n=20)
geomean                                                                  ²               -47.48%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                                           │      old      │                 new                  │
                                                           │   allocs/op   │ allocs/op   vs base                  │
EncodeQuery/#00-8                                             0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=20) ¹
EncodeQuery/#01-8                                             0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=20) ¹
EncodeQuery/oe=utf8&q=puppies-8                               8.000 ± 0%     3.000 ± 0%  -62.50% (p=0.000 n=20)
EncodeQuery/q=dogs&q=%26&q=7-8                                7.000 ± 0%     3.000 ± 0%  -57.14% (p=0.000 n=20)
EncodeQuery/a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3-8   10.000 ± 0%     5.000 ± 0%  -50.00% (p=0.000 n=20)
EncodeQuery/a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i-8            12.000 ± 0%     5.000 ± 0%  -58.33% (p=0.000 n=20)
geomean                                                                  ²               -43.23%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

Change-Id: Ia0d7579f90434f0546d93b680ab18b47a1ffbdac
GitHub-Last-Rev: f25be71e070c2c2f3a2587eea872ca52f3533c40
GitHub-Pull-Request: golang/go#75874
Reviewed-on: https://go-review.googlesource.com/c/go/+/711280
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Florian Lehner <lehner.florian86@gmail.com>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: t hepudds <thepudds1460@gmail.com>
src/net/url/url.go
src/net/url/url_test.go

index 6afa30f162bd2589d98992bdcca9f1fecc369a7f..4508f2660840367c4339ffb0a0c2924a2620a618 100644 (file)
@@ -15,7 +15,6 @@ package url
 import (
        "errors"
        "fmt"
-       "maps"
        "net/netip"
        "path"
        "slices"
@@ -1046,7 +1045,16 @@ func (v Values) Encode() string {
                return ""
        }
        var buf strings.Builder
-       for _, k := range slices.Sorted(maps.Keys(v)) {
+       // To minimize allocations, we eschew iterators and pre-size the slice in
+       // which we collect v's keys.
+       keys := make([]string, len(v))
+       var i int
+       for k := range v {
+               keys[i] = k
+               i++
+       }
+       slices.Sort(keys)
+       for _, k := range keys {
                vs := v[k]
                keyEscaped := QueryEscape(k)
                for _, v := range vs {
index 6084facacc0519b0caebd232b67d0f8edbf43898..501558403ac771eb17efbc4ce4c5fa40f9e5802f 100644 (file)
@@ -1108,6 +1108,17 @@ var encodeQueryTests = []EncodeQueryTest{
                "b": {"b1", "b2", "b3"},
                "c": {"c1", "c2", "c3"},
        }, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"},
+       {Values{
+               "a": {"a"},
+               "b": {"b"},
+               "c": {"c"},
+               "d": {"d"},
+               "e": {"e"},
+               "f": {"f"},
+               "g": {"g"},
+               "h": {"h"},
+               "i": {"i"},
+       }, "a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i"},
 }
 
 func TestEncodeQuery(t *testing.T) {
@@ -1118,6 +1129,17 @@ func TestEncodeQuery(t *testing.T) {
        }
 }
 
+func BenchmarkEncodeQuery(b *testing.B) {
+       for _, tt := range encodeQueryTests {
+               b.Run(tt.expected, func(b *testing.B) {
+                       b.ReportAllocs()
+                       for b.Loop() {
+                               tt.m.Encode()
+                       }
+               })
+       }
+}
+
 var resolvePathTests = []struct {
        base, ref, expected string
 }{