mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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/#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: f25be71e07
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>
This commit is contained in:
parent
e425176843
commit
7b81a1e107
2 changed files with 32 additions and 2 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue