Skip to content

Commit 5bd7ff1

Browse files
committed
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 ```
1 parent f317559 commit 5bd7ff1

File tree

5 files changed

+66
-4
lines changed

5 files changed

+66
-4
lines changed

Diff for: component.go

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ func (c *Component) Protocols() []Protocol {
7777
return []Protocol{c.protocol}
7878
}
7979

80+
func (c *Component) AppendProtocols(ps []Protocol) []Protocol {
81+
return append(ps, c.protocol)
82+
}
83+
8084
func (c *Component) Decapsulate(o Multiaddr) Multiaddr {
8185
if c.Equal(o) {
8286
return nil

Diff for: interface.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type Multiaddr interface {
4141
// will panic if protocol code incorrect (and bytes accessed incorrectly)
4242
Protocols() []Protocol
4343

44+
// AppendProtocols is similar to Protocols but it will reuse the extra
45+
// capacity in the slice first, this allows to prevent allocations.
46+
AppendProtocols([]Protocol) []Protocol
47+
4448
// Encapsulate wraps this Multiaddr around another. For example:
4549
//
4650
// /ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80

Diff for: makeslice.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package multiaddr
2+
3+
// makeSlice is like make([]T, len) but it perform a class size capacity
4+
// extension. In other words, if the allocation gets rounded to a bigger
5+
// allocation class, instead of wasting the unused space it is gonna return it
6+
// as extra capacity.
7+
func makeSlice[T any](len int) []T {
8+
return append([]T(nil), make([]T, len)...)
9+
}

Diff for: multiaddr.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,13 @@ func (m *multiaddr) UnmarshalJSON(data []byte) error {
106106
return err
107107
}
108108

109-
// Protocols returns the list of protocols this Multiaddr has.
110-
// will panic in case we access bytes incorrectly.
111109
func (m *multiaddr) Protocols() []Protocol {
112-
ps := make([]Protocol, 0, 8)
110+
return m.AppendProtocols(makeSlice[Protocol](8)[:0])
111+
}
112+
113+
// AppendProtocols returns the list of protocols this Multiaddr has.
114+
// will panic in case we access bytes incorrectly.
115+
func (m *multiaddr) AppendProtocols(ps []Protocol) []Protocol {
113116
b := m.bytes
114117
for len(b) > 0 {
115118
code, n, err := ReadVarintCode(b)

Diff for: multiaddr_test.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ func TestBytesSplitAndJoin(t *testing.T) {
347347
func TestProtocols(t *testing.T) {
348348
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
349349
if err != nil {
350-
t.Error("failed to construct", "/ip4/127.0.0.1/udp/1234")
350+
t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234")
351351
}
352352

353353
ps := m.Protocols()
@@ -361,6 +361,48 @@ func TestProtocols(t *testing.T) {
361361
t.Error("failed to get udp protocol")
362362
}
363363

364+
ps = m.AppendProtocols(ps)
365+
if ps[2].Code != ProtocolWithName("ip4").Code {
366+
t.Error(ps[2], ProtocolWithName("ip4"))
367+
t.Error("failed to get ip4 protocol")
368+
}
369+
370+
if ps[3].Code != ProtocolWithName("udp").Code {
371+
t.Error(ps[3], ProtocolWithName("udp"))
372+
t.Error("failed to get udp protocol")
373+
}
374+
}
375+
376+
var ProtocolSink []Protocol
377+
378+
func BenchmarkProtocols(b *testing.B) {
379+
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
380+
if err != nil {
381+
b.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234")
382+
}
383+
b.ReportAllocs()
384+
b.ResetTimer()
385+
386+
var ps []Protocol
387+
for i := b.N; i != 0; i-- {
388+
ps = m.Protocols()
389+
}
390+
ProtocolSink = ps
391+
}
392+
393+
func BenchmarkAppendProtocols(b *testing.B) {
394+
m, err := NewMultiaddr("/ip4/127.0.0.1/udp/1234")
395+
if err != nil {
396+
b.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234")
397+
}
398+
b.ReportAllocs()
399+
b.ResetTimer()
400+
401+
var ps []Protocol
402+
for i := b.N; i != 0; i-- {
403+
ps = m.AppendProtocols(ps[:0])
404+
}
405+
ProtocolSink = ps
364406
}
365407

366408
func TestProtocolsWithString(t *testing.T) {

0 commit comments

Comments
 (0)