From 8114c9db5ffc8c63e7f58eb06228ddad4ee2e1c3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 26 Jan 2021 11:11:58 -0800 Subject: [PATCH] device: reduce allocs in Device.IpcGetOperation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plenty more to go, but a start: name old time/op new time/op delta UAPIGet-4 6.37µs ± 2% 5.56µs ± 1% -12.70% (p=0.000 n=8+8) name old alloc/op new alloc/op delta UAPIGet-4 1.98kB ± 0% 1.22kB ± 0% -38.71% (p=0.000 n=10+10) name old allocs/op new allocs/op delta UAPIGet-4 42.0 ± 0% 35.0 ± 0% -16.67% (p=0.000 n=10+10) Signed-off-by: Brad Fitzpatrick --- device/uapi.go | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/device/uapi.go b/device/uapi.go index 31fbdc7..110f48a 100644 --- a/device/uapi.go +++ b/device/uapi.go @@ -7,12 +7,14 @@ package device import ( "bufio" + "bytes" "errors" "fmt" "io" "net" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -41,12 +43,19 @@ func ipcErrorf(code int64, msg string, args ...interface{}) *IPCError { return &IPCError{code: code, err: fmt.Errorf(msg, args...)} } +var byteBufferPool = &sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + // IpcGetOperation implements the WireGuard configuration protocol "get" operation. // See https://www.wireguard.com/xplatform/#configuration-protocol for details. func (device *Device) IpcGetOperation(w io.Writer) error { - lines := make([]string, 0, 100) - send := func(line string) { - lines = append(lines, line) + buf := byteBufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer byteBufferPool.Put(buf) + sendf := func(format string, args ...interface{}) { + fmt.Fprintf(buf, format, args...) + buf.WriteByte('\n') } func() { @@ -65,15 +74,15 @@ func (device *Device) IpcGetOperation(w io.Writer) error { // serialize device related values if !device.staticIdentity.privateKey.IsZero() { - send("private_key=" + device.staticIdentity.privateKey.ToHex()) + sendf("private_key=%s", device.staticIdentity.privateKey.ToHex()) } if device.net.port != 0 { - send(fmt.Sprintf("listen_port=%d", device.net.port)) + sendf("listen_port=%d", device.net.port) } if device.net.fwmark != 0 { - send(fmt.Sprintf("fwmark=%d", device.net.fwmark)) + sendf("fwmark=%d", device.net.fwmark) } // serialize each peer state @@ -82,37 +91,32 @@ func (device *Device) IpcGetOperation(w io.Writer) error { peer.RLock() defer peer.RUnlock() - send("public_key=" + peer.handshake.remoteStatic.ToHex()) - send("preshared_key=" + peer.handshake.presharedKey.ToHex()) - send("protocol_version=1") + sendf("public_key=%s", peer.handshake.remoteStatic.ToHex()) + sendf("preshared_key=%s", peer.handshake.presharedKey.ToHex()) + sendf("protocol_version=1") if peer.endpoint != nil { - send("endpoint=" + peer.endpoint.DstToString()) + sendf("endpoint=%s", peer.endpoint.DstToString()) } nano := atomic.LoadInt64(&peer.stats.lastHandshakeNano) secs := nano / time.Second.Nanoseconds() nano %= time.Second.Nanoseconds() - send(fmt.Sprintf("last_handshake_time_sec=%d", secs)) - send(fmt.Sprintf("last_handshake_time_nsec=%d", nano)) - send(fmt.Sprintf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes))) - send(fmt.Sprintf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes))) - send(fmt.Sprintf("persistent_keepalive_interval=%d", atomic.LoadUint32(&peer.persistentKeepaliveInterval))) + sendf("last_handshake_time_sec=%d", secs) + sendf("last_handshake_time_nsec=%d", nano) + sendf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes)) + sendf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes)) + sendf("persistent_keepalive_interval=%d", atomic.LoadUint32(&peer.persistentKeepaliveInterval)) for _, ip := range device.allowedips.EntriesForPeer(peer) { - send("allowed_ip=" + ip.String()) + sendf("allowed_ip=%s", ip.String()) } - } }() // send lines (does not require resource locks) - - for _, line := range lines { - _, err := io.WriteString(w, line+"\n") - if err != nil { - return ipcErrorf(ipc.IpcErrorIO, "failed to write output: %w", err) - } + if _, err := w.Write(buf.Bytes()); err != nil { + return ipcErrorf(ipc.IpcErrorIO, "failed to write output: %w", err) } return nil