Skip to content

Commit 7b81a1e

Browse files
jub0bsthepudds
authored andcommitted
net/url: reduce allocs in Encode
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/#1-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/#1-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/#1-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: f25be71 GitHub-Pull-Request: golang#75874 Reviewed-on: https://go-review.googlesource.com/c/go/+/711280 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Florian Lehner <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Sean Liao <[email protected]> Reviewed-by: t hepudds <[email protected]>
1 parent e425176 commit 7b81a1e

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

src/net/url/url.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package url
1515
import (
1616
"errors"
1717
"fmt"
18-
"maps"
1918
"net/netip"
2019
"path"
2120
"slices"
@@ -1046,7 +1045,16 @@ func (v Values) Encode() string {
10461045
return ""
10471046
}
10481047
var buf strings.Builder
1049-
for _, k := range slices.Sorted(maps.Keys(v)) {
1048+
// To minimize allocations, we eschew iterators and pre-size the slice in
1049+
// which we collect v's keys.
1050+
keys := make([]string, len(v))
1051+
var i int
1052+
for k := range v {
1053+
keys[i] = k
1054+
i++
1055+
}
1056+
slices.Sort(keys)
1057+
for _, k := range keys {
10501058
vs := v[k]
10511059
keyEscaped := QueryEscape(k)
10521060
for _, v := range vs {

src/net/url/url_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,17 @@ var encodeQueryTests = []EncodeQueryTest{
11081108
"b": {"b1", "b2", "b3"},
11091109
"c": {"c1", "c2", "c3"},
11101110
}, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"},
1111+
{Values{
1112+
"a": {"a"},
1113+
"b": {"b"},
1114+
"c": {"c"},
1115+
"d": {"d"},
1116+
"e": {"e"},
1117+
"f": {"f"},
1118+
"g": {"g"},
1119+
"h": {"h"},
1120+
"i": {"i"},
1121+
}, "a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i"},
11111122
}
11121123

11131124
func TestEncodeQuery(t *testing.T) {
@@ -1118,6 +1129,17 @@ func TestEncodeQuery(t *testing.T) {
11181129
}
11191130
}
11201131

1132+
func BenchmarkEncodeQuery(b *testing.B) {
1133+
for _, tt := range encodeQueryTests {
1134+
b.Run(tt.expected, func(b *testing.B) {
1135+
b.ReportAllocs()
1136+
for b.Loop() {
1137+
tt.m.Encode()
1138+
}
1139+
})
1140+
}
1141+
}
1142+
11211143
var resolvePathTests = []struct {
11221144
base, ref, expected string
11231145
}{

0 commit comments

Comments
 (0)