portlist: reduce allocs on Linux

Notably, it no longer allocates proportional to the number of open
sockets on the machine. Any alloc reduction numbers are a little
contrived with such a reduction but e.g. on a machine with 50,000
connections open:

name          old time/op    new time/op    delta
ParsePorts-6    57.7ms ± 6%    32.8ms ± 3%   -43.04%  (p=0.000 n=9+10)

name          old alloc/op   new alloc/op   delta
ParsePorts-6    24.0MB ± 0%     0.0MB ± 0%  -100.00%  (p=0.000 n=10+9)

name          old allocs/op  new allocs/op  delta
ParsePorts-6      100k ± 0%        0k ± 0%   -99.99%  (p=0.000 n=10+10)

Updates tailscale/corp#2566

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2021-09-11 16:45:12 -07:00
committed by Brad Fitzpatrick
parent 4f648e6fcc
commit 64e9ce8df1
2 changed files with 54 additions and 13 deletions
+19 -13
View File
@@ -17,6 +17,7 @@ import (
"syscall"
"time"
"go4.org/mem"
"golang.org/x/sys/unix"
"tailscale.com/syncs"
)
@@ -82,8 +83,11 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
return nil, err
}
fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop
var inoBuf []byte
for err == nil {
line, err := r.ReadString('\n')
line, err := r.ReadSlice('\n')
if err == io.EOF {
break
}
@@ -92,37 +96,39 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
}
// sl local rem ... inode
words := strings.Fields(line)
local := words[1]
rem := words[2]
inode := words[9]
fields = mem.AppendFields(fields[:0], mem.B(line))
local := fields[1]
rem := fields[2]
inode := fields[9]
// If a port is bound to localhost, ignore it.
// TODO: localhost is bigger than 1 IP, we need to ignore
// more things.
if strings.HasPrefix(local, v4Localhost) || strings.HasPrefix(local, v6Localhost) {
if mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost)) {
continue
}
if rem != v4Any && rem != v6Any {
if !rem.Equal(mem.S(v4Any)) && !rem.Equal(mem.S(v6Any)) {
// not a "listener" port
continue
}
// Don't use strings.Split here, because it causes
// allocations significant enough to show up in profiles.
i := strings.IndexByte(local, ':')
i := mem.IndexByte(local, ':')
if i == -1 {
return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local)
return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local.StringCopy())
}
portv, err := strconv.ParseUint(local[i+1:], 16, 16)
portv, err := mem.ParseUint(local.SliceFrom(i+1), 16, 16)
if err != nil {
return nil, fmt.Errorf("%#v: %s", local[9:], err)
return nil, fmt.Errorf("%#v: %s", local.SliceFrom(9).StringCopy(), err)
}
inodev := fmt.Sprintf("socket:[%s]", inode)
inoBuf = append(inoBuf[:0], "socket:["...)
inoBuf = mem.Append(inoBuf, inode)
inoBuf = append(inoBuf, ']')
ret = append(ret, Port{
Proto: proto,
Port: uint16(portv),
inode: inodev,
inode: string(inoBuf),
})
}