Skip to content

Commit

Permalink
btf: read all line info records in one go, instead of one by one
Browse files Browse the repository at this point in the history
`binary.Read` is able to directly read its result in a slice, and this
is way more efficient from a CPU and memory allocation standpoint

                        │   old.txt   │               new.txt               │
                        │   sec/op    │   sec/op     vs base                │
ParseLineInfoRecords-10   288.6µ ± 3%   141.3µ ± 4%  -51.02% (p=0.000 n=10)

                        │   old.txt    │            new.txt             │
                        │     B/op     │     B/op      vs base          │
ParseLineInfoRecords-10   128.0Ki ± 0%   128.0Ki ± 0%  ~ (p=1.000 n=10)

                        │    old.txt    │              new.txt               │
                        │   allocs/op   │ allocs/op   vs base                │
ParseLineInfoRecords-10   4098.000 ± 0%   3.000 ± 0%  -99.93% (p=0.000 n=10)

Signed-off-by: Paul Cacheux <[email protected]>
  • Loading branch information
paulcacheux authored and ti-mo committed Feb 5, 2025
1 parent b9d88ff commit 1bcc12e
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 11 deletions.
19 changes: 8 additions & 11 deletions btf/ext_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,20 +666,19 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
// These records appear after a btf_ext_info_sec header in the line_info
// sub-section of .BTF.ext.
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, offsetInBytes bool) ([]bpfLineInfo, error) {
var li bpfLineInfo

if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
if exp, got := uint32(binary.Size(bpfLineInfo{})), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
}

out := make([]bpfLineInfo, 0, recordNum)
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &li); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
out := make([]bpfLineInfo, recordNum)
if err := binary.Read(r, bo, out); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}

if offsetInBytes {
if offsetInBytes {
for i := range out {
li := &out[i]
if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
Expand All @@ -688,8 +687,6 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
// Convert as early as possible.
li.InsnOff /= asm.InstructionSize
}

out = append(out, li)
}

return out, nil
Expand Down
17 changes: 17 additions & 0 deletions btf/ext_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"testing"

"github.com/cilium/ebpf/internal"

"github.com/go-quicktest/qt"
)

func TestParseExtInfoBigRecordSize(t *testing.T) {
Expand Down Expand Up @@ -37,3 +39,18 @@ func BenchmarkParseLineInfoRecords(b *testing.B) {
parseLineInfoRecords(bytes.NewReader(buf), internal.NativeEndian, size, count, true)
}
}

func TestParseLineInfoRecordsAllocations(t *testing.T) {
size := uint32(binary.Size(bpfLineInfo{}))
count := uint32(4096)
buf := make([]byte, size*count)

allocs := testing.AllocsPerRun(5, func() {
parseLineInfoRecords(bytes.NewReader(buf), internal.NativeEndian, size, count, true)
})

// 7 is the number of allocations on go 1.22
// what we want to test is that we are not allocating
// once per record
qt.Assert(t, qt.IsTrue(allocs <= 7))
}

0 comments on commit 1bcc12e

Please sign in to comment.