mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 23:07:44 +00:00
portlist: further reduce allocations on Linux
Make Linux parsePorts also an append-style API and attach it to caller's provided append base memory. And add a little string intern pool in front of the []byte to string for inode names. name old time/op new time/op delta GetList-8 11.1ms ± 4% 9.8ms ± 6% -11.68% (p=0.000 n=9+10) name old alloc/op new alloc/op delta GetList-8 92.8kB ± 2% 79.7kB ± 0% -14.11% (p=0.000 n=10+9) name old allocs/op new allocs/op delta GetList-8 2.94k ± 1% 2.76k ± 0% -6.16% (p=0.000 n=10+10) More coming. (the bulk of the allocations are in addProcesses and filesystem operations, most of which we should usually be able to skip) Updates #5958 Change-Id: I3f0c03646d314a16fef7f8346aefa7d5c96701e7 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
def089f9c9
commit
7149155b80
@ -52,7 +52,7 @@ func (a *Port) lessThan(b *Port) bool {
|
||||
}
|
||||
|
||||
func (a List) sameInodes(b List) bool {
|
||||
if a == nil || b == nil || len(a) != len(b) {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -13,12 +14,14 @@
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
// Reading the sockfiles on Linux is very fast, so we can do it often.
|
||||
@ -35,13 +38,42 @@
|
||||
v4Any = "00000000:0000"
|
||||
)
|
||||
|
||||
var eofReader = bytes.NewReader(nil)
|
||||
|
||||
var bufioReaderPool = &sync.Pool{
|
||||
New: func() any { return bufio.NewReader(eofReader) },
|
||||
}
|
||||
|
||||
type internedStrings struct {
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func (v *internedStrings) get(b []byte) string {
|
||||
if s, ok := v.m[string(b)]; ok {
|
||||
return s
|
||||
}
|
||||
s := string(b)
|
||||
mak.Set(&v.m, s, s)
|
||||
return s
|
||||
}
|
||||
|
||||
var internedStringsPool = &sync.Pool{
|
||||
New: func() any { return new(internedStrings) },
|
||||
}
|
||||
|
||||
func appendListeningPorts(base []Port) ([]Port, error) {
|
||||
ret := base
|
||||
if sawProcNetPermissionErr.Load() {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var br *bufio.Reader
|
||||
br := bufioReaderPool.Get().(*bufio.Reader)
|
||||
defer bufioReaderPool.Put(br)
|
||||
defer br.Reset(eofReader)
|
||||
|
||||
stringCache := internedStringsPool.Get().(*internedStrings)
|
||||
defer internedStringsPool.Put(stringCache)
|
||||
|
||||
for _, fname := range sockfiles {
|
||||
// Android 10+ doesn't allow access to this anymore.
|
||||
// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
|
||||
@ -59,25 +91,24 @@ func appendListeningPorts(base []Port) ([]Port, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", fname, err)
|
||||
}
|
||||
if br == nil {
|
||||
br = bufio.NewReader(f)
|
||||
} else {
|
||||
br.Reset(f)
|
||||
}
|
||||
ports, err := parsePorts(br, filepath.Base(fname))
|
||||
br.Reset(f)
|
||||
ret, err = appendParsePorts(ret, stringCache, br, filepath.Base(fname))
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %q: %w", fname, err)
|
||||
}
|
||||
ret = append(ret, ports...)
|
||||
}
|
||||
if len(stringCache.m) >= len(ret)*2 {
|
||||
// Prevent unbounded growth of the internedStrings map.
|
||||
stringCache.m = nil
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// fileBase is one of "tcp", "tcp6", "udp", "udp6".
|
||||
func parsePorts(r *bufio.Reader, fileBase string) ([]Port, error) {
|
||||
func appendParsePorts(base []Port, stringCache *internedStrings, r *bufio.Reader, fileBase string) ([]Port, error) {
|
||||
proto := strings.TrimSuffix(fileBase, "6")
|
||||
var ret []Port
|
||||
ret := base
|
||||
|
||||
// skip header row
|
||||
_, err := r.ReadSlice('\n')
|
||||
@ -171,7 +202,7 @@ func parsePorts(r *bufio.Reader, fileBase string) ([]Port, error) {
|
||||
ret = append(ret, Port{
|
||||
Proto: proto,
|
||||
Port: uint16(portv),
|
||||
inode: string(inoBuf),
|
||||
inode: stringCache.get(inoBuf),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,7 @@ func TestParsePorts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stringCache := new(internedStrings)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
buf := bytes.NewBufferString(tt.in)
|
||||
@ -84,7 +85,7 @@ func TestParsePorts(t *testing.T) {
|
||||
if tt.file != "" {
|
||||
file = tt.file
|
||||
}
|
||||
got, err := parsePorts(r, file)
|
||||
got, err := appendParsePorts(nil, stringCache, r, file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -116,11 +117,12 @@ func BenchmarkParsePorts(b *testing.B) {
|
||||
|
||||
r := bytes.NewReader(contents.Bytes())
|
||||
br := bufio.NewReader(&contents)
|
||||
stringCache := new(internedStrings)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, io.SeekStart)
|
||||
br.Reset(r)
|
||||
got, err := parsePorts(br, "tcp6")
|
||||
got, err := appendParsePorts(nil, stringCache, br, "tcp6")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user