Skip to content

Commit

Permalink
Reduce bytes to string allocs in decoder
Browse files Browse the repository at this point in the history
Instead of allocating when converting
small byte slices to strings in the decoder,
we can write to a 4kb buffer and use unsafe.String
which reffers to that buffer.
This approach was successfully tested in
https://github.com/marselester/systemd which is used
by https://github.com/parca-dev/parca-agent.

Note, BenchmarkUnixFDs showed 8 allocs reduction.
With larger dbus messages, the savings should be significant.
  • Loading branch information
marselester committed Apr 23, 2023
1 parent a2da220 commit 3418e0c
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 6 deletions.
59 changes: 57 additions & 2 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/binary"
"io"
"reflect"
"unsafe"
)

type decoder struct {
Expand All @@ -12,6 +13,7 @@ type decoder struct {
pos int
fds []int

conv *stringConverter
// The following fields are used to reduce memory allocs.
buf []byte
d float64
Expand All @@ -25,6 +27,7 @@ func newDecoder(in io.Reader, order binary.ByteOrder, fds []int) *decoder {
dec.in = in
dec.order = order
dec.fds = fds
dec.conv = newStringConverter(stringConverterBufferSize)
return dec
}

Expand All @@ -34,6 +37,10 @@ func (dec *decoder) Reset(in io.Reader, order binary.ByteOrder, fds []int) {
dec.order = order
dec.pos = 0
dec.fds = fds

if dec.conv == nil {
dec.conv = newStringConverter(stringConverterBufferSize)
}
}

// align aligns the input to the given boundary and panics on error.
Expand Down Expand Up @@ -148,7 +155,7 @@ func (dec *decoder) decode(s string, depth int) interface{} {
p := int(length) + 1
dec.read2buf(p)
dec.pos += p
return string(dec.buf[:len(dec.buf)-1])
return dec.conv.String(dec.buf[:len(dec.buf)-1])
case 'o':
return ObjectPath(dec.decode("s", depth).(string))
case 'g':
Expand All @@ -157,7 +164,7 @@ func (dec *decoder) decode(s string, depth int) interface{} {
dec.read2buf(p)
dec.pos += p
sig, err := ParseSignature(
string(dec.buf[:len(dec.buf)-1]),
dec.conv.String(dec.buf[:len(dec.buf)-1]),
)
if err != nil {
panic(err)
Expand Down Expand Up @@ -310,3 +317,51 @@ type FormatError string
func (e FormatError) Error() string {
return "dbus: wire format error: " + string(e)
}

const stringConverterBufferSize = 4096

func newStringConverter(capacity int) *stringConverter {
return &stringConverter{
buf: make([]byte, 0, capacity),
offset: 0,
}
}

// stringConverter converts bytes to strings with less allocs.
// The idea is to accumulate bytes in a buffer with specified capacity
// and create strings with unsafe.String using bytes from a buffer.
// For example, 10 "fizz" strings written to a 40-byte buffer
// will result in 1 alloc instead of 10.
//
// Once a buffer is filled, a new one is created with the same capacity.
// Old buffers will be eventually GC-ed
// with no side effects to the returned strings.
type stringConverter struct {
// buf is a temporary buffer where decoded strings are batched.
buf []byte
// offset is a buffer position where the last string was written.
offset int
}

// String converts bytes to a string.
func (c *stringConverter) String(b []byte) string {
n := len(b)
if n == 0 {
return ""
}
// Must allocate because a string doesn't fit into the buffer.
if n > cap(c.buf) {
return string(b)
}

if len(c.buf)+n > cap(c.buf) {
c.buf = make([]byte, 0, cap(c.buf))
c.offset = 0
}
c.buf = append(c.buf, b...)

b = c.buf[c.offset:]
s := unsafe.String(&b[0], n)
c.offset += n
return s
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/godbus/dbus/v5

go 1.12
go 1.20

require golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect
require golang.org/x/sys v0.7.0
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

0 comments on commit 3418e0c

Please sign in to comment.