mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
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:
parent
4f648e6fcc
commit
64e9ce8df1
@ -17,6 +17,7 @@
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
)
|
)
|
||||||
@ -82,8 +83,11 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop
|
||||||
|
|
||||||
|
var inoBuf []byte
|
||||||
for err == nil {
|
for err == nil {
|
||||||
line, err := r.ReadString('\n')
|
line, err := r.ReadSlice('\n')
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -92,37 +96,39 @@ func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sl local rem ... inode
|
// sl local rem ... inode
|
||||||
words := strings.Fields(line)
|
fields = mem.AppendFields(fields[:0], mem.B(line))
|
||||||
local := words[1]
|
local := fields[1]
|
||||||
rem := words[2]
|
rem := fields[2]
|
||||||
inode := words[9]
|
inode := fields[9]
|
||||||
|
|
||||||
// If a port is bound to localhost, ignore it.
|
// If a port is bound to localhost, ignore it.
|
||||||
// TODO: localhost is bigger than 1 IP, we need to ignore
|
// TODO: localhost is bigger than 1 IP, we need to ignore
|
||||||
// more things.
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
if rem != v4Any && rem != v6Any {
|
if !rem.Equal(mem.S(v4Any)) && !rem.Equal(mem.S(v6Any)) {
|
||||||
// not a "listener" port
|
// not a "listener" port
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't use strings.Split here, because it causes
|
// Don't use strings.Split here, because it causes
|
||||||
// allocations significant enough to show up in profiles.
|
// allocations significant enough to show up in profiles.
|
||||||
i := strings.IndexByte(local, ':')
|
i := mem.IndexByte(local, ':')
|
||||||
if i == -1 {
|
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 {
|
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{
|
ret = append(ret, Port{
|
||||||
Proto: proto,
|
Proto: proto,
|
||||||
Port: uint16(portv),
|
Port: uint16(portv),
|
||||||
inode: inodev,
|
inode: string(inoBuf),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -65,3 +66,37 @@ func TestParsePorts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkParsePorts(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
var contents bytes.Buffer
|
||||||
|
contents.WriteString(` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||||
|
0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 35720 1 0000000000000000 100 0 0 10 0
|
||||||
|
1: 00000000000000000000000000000000:1F91 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 142240557 1 0000000000000000 100 0 0 10 0
|
||||||
|
2: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 34064 1 0000000000000000 100 0 0 10 0
|
||||||
|
`)
|
||||||
|
for i := 0; i < 50000; i++ {
|
||||||
|
contents.WriteString(" 3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000 1000 0 151042856 2 0000000000000000 21 4 28 10 -1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []Port{
|
||||||
|
{Proto: "tcp", Port: 8081, inode: "socket:[142240557]"},
|
||||||
|
{Proto: "tcp", Port: 22, inode: "socket:[34064]"},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(contents.Bytes())
|
||||||
|
br := bufio.NewReader(&contents)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
r.Seek(0, io.SeekStart)
|
||||||
|
br.Reset(r)
|
||||||
|
got, err := parsePorts(br, "tcp")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(got) != 2 || got[0].Port != 8081 || got[1].Port != 22 {
|
||||||
|
b.Fatalf("wrong result:\n got %+v\nwant %+v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user