Skip to content

Commit

Permalink
fix: improve performance of readStringVarFromBuff
Browse files Browse the repository at this point in the history
Optimize readStringVarFromBuff for better performance and memory usage.

- The optimizations were tested using new benchmarks:
  - Short: 33.26% faster, 42.86% less memory usage, 1 fewer allocation.
  - Medium: 28.34% faster, 54.55% less memory usage, 1 fewer allocation.
  - Long: 26.90% faster, 73.42% less memory usage, 3 fewer allocations.
  - Long: with Low Max: 19.12% faster, 48.15% less memory usage, 1 fewer
    allocation.

The overall improvements show significant gains in both execution speed
and memory efficiency. For more check eventsreader_bench_test.go.

Changes:
- Preallocated the buffer with a reasonable initial capacity to avoid
  repeated slice resizing.
- Removed TrimLeft calls since the decoded buffer is converted to a
  string right after.
  • Loading branch information
geyslan committed Jul 18, 2024
1 parent 772c609 commit 7a59029
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 6 deletions.
25 changes: 19 additions & 6 deletions pkg/bufferdecoder/eventsreader.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package bufferdecoder

import (
"bytes"
"encoding/binary"
"net"
"strconv"
Expand Down Expand Up @@ -373,21 +372,35 @@ func readStringFromBuff(ebpfMsgDecoder *EbpfDecoder) (string, error) {
func readStringVarFromBuff(decoder *EbpfDecoder, max int) (string, error) {
var err error
var char int8
res := make([]byte, max)
var res []byte

switch {
case max > 0:
res = make([]byte, 0, max)
case max == 0:
res = make([]byte, 0)
default:
return "", errfmt.Errorf("invalid max length: %d", max)
}

err = decoder.DecodeInt8(&char)
if err != nil {
return "", errfmt.Errorf("error reading null terminated string: %v", err)
}
var count int
for count = 1; char != 0 && count < max; count++ {

count := 1 // first char is already decoded
for char != 0 && count < max {
res = append(res, byte(char))

// decode next char
err = decoder.DecodeInt8(&char)
if err != nil {
return "", errfmt.Errorf("error reading null terminated string: %v", err)
}
count++
}
res = bytes.TrimLeft(res[:], "\000")
decoder.cursor += max - count // move cursor to end of buffer

decoder.cursor += max - count // move cursor to the end of the buffer
return string(res), nil
}

Expand Down
56 changes: 56 additions & 0 deletions pkg/bufferdecoder/eventsreader_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package bufferdecoder

import (
"bytes"
"testing"
)

func BenchmarkReadStringVarFromBuff_ShortString(b *testing.B) {
buffer := []byte{'H', 'e', 'l', 'l', 'o', 0}
max := 10
var str string

for i := 0; i < b.N; i++ {
decoder := New(buffer)
str, _ = readStringVarFromBuff(decoder, max)
}
_ = str
}

func BenchmarkReadStringVarFromBuff_MediumString(b *testing.B) {
buffer := []byte{'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', 0}
max := 20
var str string

for i := 0; i < b.N; i++ {
decoder := New(buffer)
str, _ = readStringVarFromBuff(decoder, max)
}
_ = str
}

func BenchmarkReadStringVarFromBuff_LongString(b *testing.B) {
buffer := append(bytes.Repeat([]byte{'A'}, 10000), 0)
max := 10000
var str string

b.ResetTimer()
for i := 0; i < b.N; i++ {
decoder := New(buffer)
str, _ = readStringVarFromBuff(decoder, max)
}
_ = str
}

func BenchmarkReadStringVarFromBuff_LongStringLowMax(b *testing.B) {
buffer := bytes.Repeat([]byte{'A'}, 10000)
max := 100
var str string

b.ResetTimer()
for i := 0; i < b.N; i++ {
decoder := New(buffer)
str, _ = readStringVarFromBuff(decoder, max)
}
_ = str
}
74 changes: 74 additions & 0 deletions pkg/bufferdecoder/eventsreader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,80 @@ func TestReadArgFromBuff(t *testing.T) {
}
}

func TestReadStringVarFromBuff(t *testing.T) {
tests := []struct {
name string
buffer []byte
max int
expected string
expectedCursor int
expectError bool
}{
{
name: "Null terminated string in larger buffer",
buffer: []byte{'H', 'e', 'l', 'l', 'o', 0, 'W', 'o', 'r', 'l', 'd'},
max: 6,
expected: "Hello",
expectedCursor: 6,
expectError: false,
},
{
name: "Buffer with same length as max without null terminator",
buffer: []byte{'H', 'e', 'l', 'l', 'o'},
max: 5,
expected: "Hell",
expectedCursor: 5,
expectError: false,
},
{
name: "Buffer longer than max length without null terminator",
buffer: []byte{'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'},
max: 5,
expected: "Hell",
expectedCursor: 5,
expectError: false,
},
{
name: "Zero max length",
buffer: []byte{'H', 'e', 'l', 'l', 'o', 0, 'W', 'o', 'r', 'l', 'd'},
max: 0,
expected: "",
expectedCursor: 0,
expectError: false,
},
{
name: "Empty buffer",
buffer: []byte{},
max: 5,
expected: "",
expectedCursor: 0,
expectError: true,
},
{
name: "Buffer too short",
buffer: []byte{'H'},
max: 5,
expected: "H",
expectedCursor: 0,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
decoder := New(tt.buffer)
actual, err := readStringVarFromBuff(decoder, tt.max)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, actual)
assert.Equal(t, tt.expectedCursor, decoder.ReadAmountBytes())
}
})
}
}

func TestPrintUint32IP(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 7a59029

Please sign in to comment.