From 5bd7ff17630bdb19d942131b34330d17beed0c5c Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 4 Jan 2023 10:51:18 +0100 Subject: [PATCH] perf: Add AppendProtocols for a an allocation free to get the protocols While looking at Kubo benchmarks, if you are using connection filters you allocate ~200MiB/s in the Protocols code path. This new method allows to give some preallocated memory in a slice, and it will be reused instead of allocating more. ``` goos: linux goarch: amd64 pkg: github.com/multiformats/go-multiaddr cpu: AMD Ryzen 5 3600 6-Core Processor BenchmarkProtocols-12 3779694 312.0 ns/op 640 B/op 1 allocs/op BenchmarkAppendProtocols-12 26105854 43.13 ns/op 0 B/op 0 allocs/op ``` --- component.go | 4 ++++ interface.go | 4 ++++ makeslice.go | 9 +++++++++ multiaddr.go | 9 ++++++--- multiaddr_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 makeslice.go diff --git a/component.go b/component.go index 490b8ac..3321e97 100644 --- a/component.go +++ b/component.go @@ -77,6 +77,10 @@ func (c *Component) Protocols() []Protocol { return []Protocol{c.protocol} } +func (c *Component) AppendProtocols(ps []Protocol) []Protocol { + return append(ps, c.protocol) +} + func (c *Component) Decapsulate(o Multiaddr) Multiaddr { if c.Equal(o) { return nil diff --git a/interface.go b/interface.go index 699c54d..94100bf 100644 --- a/interface.go +++ b/interface.go @@ -41,6 +41,10 @@ type Multiaddr interface { // will panic if protocol code incorrect (and bytes accessed incorrectly) Protocols() []Protocol + // AppendProtocols is similar to Protocols but it will reuse the extra + // capacity in the slice first, this allows to prevent allocations. + AppendProtocols([]Protocol) []Protocol + // Encapsulate wraps this Multiaddr around another. For example: // // /ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80 diff --git a/makeslice.go b/makeslice.go new file mode 100644 index 0000000..5cfad28 --- /dev/null +++ b/makeslice.go @@ -0,0 +1,9 @@ +package multiaddr + +// makeSlice is like make([]T, len) but it perform a class size capacity +// extension. In other words, if the allocation gets rounded to a bigger +// allocation class, instead of wasting the unused space it is gonna return it +// as extra capacity. +func makeSlice[T any](len int) []T { + return append([]T(nil), make([]T, len)...) +} diff --git a/multiaddr.go b/multiaddr.go index af5d7f1..df09e69 100644 --- a/multiaddr.go +++ b/multiaddr.go @@ -106,10 +106,13 @@ func (m *multiaddr) UnmarshalJSON(data []byte) error { return err } -// Protocols returns the list of protocols this Multiaddr has. -// will panic in case we access bytes incorrectly. func (m *multiaddr) Protocols() []Protocol { - ps := make([]Protocol, 0, 8) + return m.AppendProtocols(makeSlice[Protocol](8)[:0]) +} + +// AppendProtocols returns the list of protocols this Multiaddr has. +// will panic in case we access bytes incorrectly. +func (m *multiaddr) AppendProtocols(ps []Protocol) []Protocol { b := m.bytes for len(b) > 0 { code, n, err := ReadVarintCode(b) diff --git a/multiaddr_test.go b/multiaddr_test.go index fc0f2a9..e2d8763 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -347,7 +347,7 @@ func TestBytesSplitAndJoin(t *testing.T) { func TestProtocols(t *testing.T) { m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") if err != nil { - t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234") + t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234") } ps := m.Protocols() @@ -361,6 +361,48 @@ func TestProtocols(t *testing.T) { t.Error("failed to get udp protocol") } + ps = m.AppendProtocols(ps) + if ps[2].Code != ProtocolWithName("ip4").Code { + t.Error(ps[2], ProtocolWithName("ip4")) + t.Error("failed to get ip4 protocol") + } + + if ps[3].Code != ProtocolWithName("udp").Code { + t.Error(ps[3], ProtocolWithName("udp")) + t.Error("failed to get udp protocol") + } +} + +var ProtocolSink []Protocol + +func BenchmarkProtocols(b *testing.B) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + b.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234") + } + b.ReportAllocs() + b.ResetTimer() + + var ps []Protocol + for i := b.N; i != 0; i-- { + ps = m.Protocols() + } + ProtocolSink = ps +} + +func BenchmarkAppendProtocols(b *testing.B) { + m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234") + if err != nil { + b.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234") + } + b.ReportAllocs() + b.ResetTimer() + + var ps []Protocol + for i := b.N; i != 0; i-- { + ps = m.AppendProtocols(ps[:0]) + } + ProtocolSink = ps } func TestProtocolsWithString(t *testing.T) {