Skip to content

Commit

Permalink
perf: Add AppendProtocols for a an allocation free to get the protocols
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
Jorropo committed Jan 4, 2023
1 parent f317559 commit 37d7f3e
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 4 deletions.
4 changes: 4 additions & 0 deletions component.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions makeslice.go
Original file line number Diff line number Diff line change
@@ -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)...)
}
9 changes: 6 additions & 3 deletions multiaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
44 changes: 43 additions & 1 deletion multiaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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[0], ProtocolWithName("ip4"))
t.Error("failed to get ip4 protocol")
}

if ps[3].Code != ProtocolWithName("udp").Code {
t.Error(ps[1], 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) {
Expand Down

0 comments on commit 37d7f3e

Please sign in to comment.