Skip to content

Commit a295890

Browse files
Thorleonmvdan
authored andcommitted
net: precompute rfc6724policyTable in addrselect
As net package has one of the biggest init time in standard library, I have tried to improve performance by doing two things in net/addrselect.go: 1. Precompute slice with RFC rules. Currently the rules are computed and sorted in init() function. We could save the time and allocations by using prepopulated values in sorted manner. The rules haven't changed since 2015. To be extra safe we could move order validation as test case. It should slightly speed up startup of each binary with "net" package and go dns resolver. It also saves 38 allocations, ~50% of allocations in init phase of `net` module. 2. Replace internal net.IP usage with netip.Addr in `sortByRFC6724` function. It results in ~40% performance improvement on samples from tests. The only risk is the difference between net.IP and netip.Addr behaviour. Init benchmark: Init-8 1.89µs ± 2% 0.12µs ± 3% -93.79% (p=0.000 n=5+5) name old alloc/op new alloc/op delta Init-8 1.05kB ± 0% 0.38kB ± 0% ~ (zero variance) name old allocs/op new allocs/op delta Init-8 39.0 ± 0% 1.0 ± 0% ~ (zero variance) Whole sortByRFC6724 function benchmark: name old time/op new time/op delta SortByRFC6724/0-8 463ns ± 3% 303ns ± 4% -34.72% (p=0.000 n=5+5) SortByRFC6724/1-8 481ns ± 8% 306ns ± 1% -36.46% (p=0.000 n=5+5) SortByRFC6724/2-8 470ns ± 4% 307ns ± 4% -34.77% (p=0.000 n=5+5) SortByRFC6724/3-8 567ns ± 3% 367ns ± 3% -35.28% (p=0.000 n=5+5) SortByRFC6724/4-8 918ns ± 3% 560ns ± 2% -38.93% (p=0.000 n=5+5) Updates #54032 Change-Id: Ic18df1ea73805cb184c6ceb73470ca7f0b922032 Reviewed-on: https://go-review.googlesource.com/c/go/+/419356 Reviewed-by: Heschi Kreinick <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent 3fbcf05 commit a295890

File tree

2 files changed

+191
-140
lines changed

2 files changed

+191
-140
lines changed

src/net/addrselect.go

Lines changed: 68 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package net
88

9-
import "sort"
9+
import (
10+
"net/netip"
11+
"sort"
12+
)
1013

1114
func sortByRFC6724(addrs []IPAddr) {
1215
if len(addrs) < 2 {
@@ -15,14 +18,15 @@ func sortByRFC6724(addrs []IPAddr) {
1518
sortByRFC6724withSrcs(addrs, srcAddrs(addrs))
1619
}
1720

18-
func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) {
21+
func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr) {
1922
if len(addrs) != len(srcs) {
2023
panic("internal error")
2124
}
2225
addrAttr := make([]ipAttr, len(addrs))
2326
srcAttr := make([]ipAttr, len(srcs))
2427
for i, v := range addrs {
25-
addrAttr[i] = ipAttrOf(v.IP)
28+
addrAttrIP, _ := netip.AddrFromSlice(v.IP)
29+
addrAttr[i] = ipAttrOf(addrAttrIP)
2630
srcAttr[i] = ipAttrOf(srcs[i])
2731
}
2832
sort.Stable(&byRFC6724{
@@ -36,16 +40,16 @@ func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) {
3640
// srcsAddrs tries to UDP-connect to each address to see if it has a
3741
// route. (This doesn't send any packets). The destination port
3842
// number is irrelevant.
39-
func srcAddrs(addrs []IPAddr) []IP {
40-
srcs := make([]IP, len(addrs))
43+
func srcAddrs(addrs []IPAddr) []netip.Addr {
44+
srcs := make([]netip.Addr, len(addrs))
4145
dst := UDPAddr{Port: 9}
4246
for i := range addrs {
4347
dst.IP = addrs[i].IP
4448
dst.Zone = addrs[i].Zone
4549
c, err := DialUDP("udp", nil, &dst)
4650
if err == nil {
4751
if src, ok := c.LocalAddr().(*UDPAddr); ok {
48-
srcs[i] = src.IP
52+
srcs[i], _ = netip.AddrFromSlice(src.IP)
4953
}
5054
c.Close()
5155
}
@@ -59,8 +63,8 @@ type ipAttr struct {
5963
Label uint8
6064
}
6165

62-
func ipAttrOf(ip IP) ipAttr {
63-
if ip == nil {
66+
func ipAttrOf(ip netip.Addr) ipAttr {
67+
if !ip.IsValid() {
6468
return ipAttr{}
6569
}
6670
match := rfc6724policyTable.Classify(ip)
@@ -74,7 +78,7 @@ func ipAttrOf(ip IP) ipAttr {
7478
type byRFC6724 struct {
7579
addrs []IPAddr // addrs to sort
7680
addrAttr []ipAttr
77-
srcs []IP // or nil if unreachable
81+
srcs []netip.Addr // or not valid addr if unreachable
7882
srcAttr []ipAttr
7983
}
8084

@@ -108,13 +112,13 @@ func (s *byRFC6724) Less(i, j int) bool {
108112
// If DB is known to be unreachable or if Source(DB) is undefined, then
109113
// prefer DA. Similarly, if DA is known to be unreachable or if
110114
// Source(DA) is undefined, then prefer DB.
111-
if SourceDA == nil && SourceDB == nil {
115+
if !SourceDA.IsValid() && !SourceDB.IsValid() {
112116
return false // "equal"
113117
}
114-
if SourceDB == nil {
118+
if !SourceDB.IsValid() {
115119
return preferDA
116120
}
117-
if SourceDA == nil {
121+
if !SourceDA.IsValid() {
118122
return preferDB
119123
}
120124

@@ -184,7 +188,7 @@ func (s *byRFC6724) Less(i, j int) bool {
184188
return preferDB
185189
}
186190

187-
// Rule 9: Use longest matching prefix.
191+
// Rule 9: Use the longest matching prefix.
188192
// When DA and DB belong to the same address family (both are IPv6 or
189193
// both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) >
190194
// CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if
@@ -212,98 +216,83 @@ func (s *byRFC6724) Less(i, j int) bool {
212216
}
213217

214218
type policyTableEntry struct {
215-
Prefix *IPNet
219+
Prefix netip.Prefix
216220
Precedence uint8
217221
Label uint8
218222
}
219223

220224
type policyTable []policyTableEntry
221225

222226
// RFC 6724 section 2.1.
227+
// Items are sorted by the size of their Prefix.Mask.Size,
223228
var rfc6724policyTable = policyTable{
224229
{
225-
Prefix: mustCIDR("::1/128"),
230+
// "::1/128"
231+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128),
226232
Precedence: 50,
227233
Label: 0,
228234
},
229235
{
230-
Prefix: mustCIDR("::/0"),
231-
Precedence: 40,
232-
Label: 1,
233-
},
234-
{
236+
// "::ffff:0:0/96"
235237
// IPv4-compatible, etc.
236-
Prefix: mustCIDR("::ffff:0:0/96"),
238+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96),
237239
Precedence: 35,
238240
Label: 4,
239241
},
240242
{
241-
// 6to4
242-
Prefix: mustCIDR("2002::/16"),
243-
Precedence: 30,
244-
Label: 2,
243+
// "::/96"
244+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96),
245+
Precedence: 1,
246+
Label: 3,
245247
},
246248
{
249+
// "2001::/32"
247250
// Teredo
248-
Prefix: mustCIDR("2001::/32"),
251+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32),
249252
Precedence: 5,
250253
Label: 5,
251254
},
252255
{
253-
Prefix: mustCIDR("fc00::/7"),
254-
Precedence: 3,
255-
Label: 13,
256+
// "2002::/16"
257+
// 6to4
258+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16),
259+
Precedence: 30,
260+
Label: 2,
256261
},
257262
{
258-
Prefix: mustCIDR("::/96"),
263+
// "3ffe::/16"
264+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16),
259265
Precedence: 1,
260-
Label: 3,
266+
Label: 12,
261267
},
262268
{
263-
Prefix: mustCIDR("fec0::/10"),
269+
// "fec0::/10"
270+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10),
264271
Precedence: 1,
265272
Label: 11,
266273
},
267274
{
268-
Prefix: mustCIDR("3ffe::/16"),
269-
Precedence: 1,
270-
Label: 12,
275+
// "fc00::/7"
276+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7),
277+
Precedence: 3,
278+
Label: 13,
279+
},
280+
{
281+
// "::/0"
282+
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
283+
Precedence: 40,
284+
Label: 1,
271285
},
272-
}
273-
274-
func init() {
275-
sort.Sort(sort.Reverse(byMaskLength(rfc6724policyTable)))
276-
}
277-
278-
// byMaskLength sorts policyTableEntry by the size of their Prefix.Mask.Size,
279-
// from smallest mask, to largest.
280-
type byMaskLength []policyTableEntry
281-
282-
func (s byMaskLength) Len() int { return len(s) }
283-
func (s byMaskLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
284-
func (s byMaskLength) Less(i, j int) bool {
285-
isize, _ := s[i].Prefix.Mask.Size()
286-
jsize, _ := s[j].Prefix.Mask.Size()
287-
return isize < jsize
288-
}
289-
290-
// mustCIDR calls ParseCIDR and panics on any error, or if the network
291-
// is not IPv6.
292-
func mustCIDR(s string) *IPNet {
293-
ip, ipNet, err := ParseCIDR(s)
294-
if err != nil {
295-
panic(err.Error())
296-
}
297-
if len(ip) != IPv6len {
298-
panic("unexpected IP length")
299-
}
300-
return ipNet
301286
}
302287

303288
// Classify returns the policyTableEntry of the entry with the longest
304289
// matching prefix that contains ip.
305290
// The table t must be sorted from largest mask size to smallest.
306-
func (t policyTable) Classify(ip IP) policyTableEntry {
291+
func (t policyTable) Classify(ip netip.Addr) policyTableEntry {
292+
// Prefix.Contains() will not match an IPv6 prefix for an IPv4 address.
293+
if ip.Is4() {
294+
ip = netip.AddrFrom16(ip.As16())
295+
}
307296
for _, ent := range t {
308297
if ent.Prefix.Contains(ip) {
309298
return ent
@@ -324,17 +313,18 @@ const (
324313
scopeGlobal scope = 0xe
325314
)
326315

327-
func classifyScope(ip IP) scope {
316+
func classifyScope(ip netip.Addr) scope {
328317
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
329318
return scopeLinkLocal
330319
}
331-
ipv6 := len(ip) == IPv6len && ip.To4() == nil
320+
ipv6 := ip.Is6() && !ip.Is4In6()
321+
ipv6AsBytes := ip.As16()
332322
if ipv6 && ip.IsMulticast() {
333-
return scope(ip[1] & 0xf)
323+
return scope(ipv6AsBytes[1] & 0xf)
334324
}
335325
// Site-local addresses are defined in RFC 3513 section 2.5.6
336326
// (and deprecated in RFC 3879).
337-
if ipv6 && ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 {
327+
if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 {
338328
return scopeSiteLocal
339329
}
340330
return scopeGlobal
@@ -350,30 +340,28 @@ func classifyScope(ip IP) scope {
350340
// If a and b are different IP versions, 0 is returned.
351341
//
352342
// See https://tools.ietf.org/html/rfc6724#section-2.2
353-
func commonPrefixLen(a, b IP) (cpl int) {
354-
if a4 := a.To4(); a4 != nil {
355-
a = a4
356-
}
343+
func commonPrefixLen(a netip.Addr, b IP) (cpl int) {
357344
if b4 := b.To4(); b4 != nil {
358345
b = b4
359346
}
360-
if len(a) != len(b) {
347+
aAsSlice := a.AsSlice()
348+
if len(aAsSlice) != len(b) {
361349
return 0
362350
}
363351
// If IPv6, only up to the prefix (first 64 bits)
364-
if len(a) > 8 {
365-
a = a[:8]
352+
if len(aAsSlice) > 8 {
353+
aAsSlice = aAsSlice[:8]
366354
b = b[:8]
367355
}
368-
for len(a) > 0 {
369-
if a[0] == b[0] {
356+
for len(aAsSlice) > 0 {
357+
if aAsSlice[0] == b[0] {
370358
cpl += 8
371-
a = a[1:]
359+
aAsSlice = aAsSlice[1:]
372360
b = b[1:]
373361
continue
374362
}
375363
bits := 8
376-
ab, bb := a[0], b[0]
364+
ab, bb := aAsSlice[0], b[0]
377365
for {
378366
ab >>= 1
379367
bb >>= 1

0 commit comments

Comments
 (0)