Skip to content

Commit

Permalink
btf: Replace binary.Read with manual parsing in readAndInflateTypes
Browse files Browse the repository at this point in the history
During profiling `binary.Read` uses up a significant amount of CPU time.
This seems to be due to it using reflection to calculate the amount of
bytes to read at runtime and not caching these results.

By doing manual `io.ReadFull` calls, pre-calculating struct sizes and
reusing types and buffers where possible, we can reduce the CPU time
spent in `readAndInflateTypes` by almost 25%.

```
goos: linux
goarch: amd64
pkg: github.com/cilium/ebpf/btf
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
                │ before.txt  │          after.txt           │
                │   sec/op    │   sec/op     vs base         │
ParseVmlinux-16   46.08m ± 1%   34.59m ± 2%  -24.93% (n=100)

                │  before.txt  │           after.txt           │
                │     B/op     │     B/op      vs base         │
ParseVmlinux-16   26.65Mi ± 0%   23.49Mi ± 0%  -11.87% (n=100)

                │ before.txt  │          after.txt           │
                │  allocs/op  │  allocs/op   vs base         │
ParseVmlinux-16   467.5k ± 0%   267.7k ± 0%  -42.73% (n=100)
```

Signed-off-by: Dylan Reimerink <dylan.reimerink@isovalent.com>
  • Loading branch information
dylandreimerink committed Nov 7, 2023
1 parent b66beaa commit 97891cc
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 35 deletions.
165 changes: 165 additions & 0 deletions btf/btf_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"reflect"
"unsafe"

"github.com/cilium/ebpf/internal"
Expand Down Expand Up @@ -153,6 +154,19 @@ type btfType struct {
SizeType uint32
}

var btfTypeSize = packedSize[btfType]()

func unmarshalBtfType(bt *btfType, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfTypeSize {
return 0, fmt.Errorf("not enough bytes to unmarshal btfType")
}

bt.NameOff = bo.Uint32(b[0:])
bt.Info = bo.Uint32(b[4:])
bt.SizeType = bo.Uint32(b[8:])
return btfTypeSize, nil
}

func mask(len uint32) uint32 {
return (1 << len) - 1
}
Expand Down Expand Up @@ -280,6 +294,18 @@ func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error {
return binary.Write(w, bo, rt.data)
}

// packedSize returns the size of a struct in bytes without padding.
// Unlike unsafe.Sizeof, which returns the size of a struct with padding.
func packedSize[T any]() int {
var t T
typ := reflect.TypeOf(t)
size := 0
for i := 0; i < typ.NumField(); i++ {
size += int(typ.Field(i).Type.Size())
}
return size
}

// btfInt encodes additional data for integers.
//
// ? ? ? ? e e e e o o o o o o o o ? ? ? ? ? ? ? ? b b b b b b b b
Expand All @@ -300,6 +326,17 @@ const (
btfIntBitsShift = 0
)

var btfIntLen = packedSize[btfInt]()

func unmarshalBtfInt(bi *btfInt, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfIntLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfInt")
}

bi.Raw = bo.Uint32(b[0:])
return btfIntLen, nil
}

func (bi btfInt) Encoding() IntEncoding {
return IntEncoding(readBits(bi.Raw, btfIntEncodingLen, btfIntEncodingShift))
}
Expand Down Expand Up @@ -330,38 +367,166 @@ type btfArray struct {
Nelems uint32
}

var btfArrayLen = packedSize[btfArray]()

func unmarshalBtfArray(ba *btfArray, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfArrayLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfArray")
}

ba.Type = TypeID(bo.Uint32(b[0:]))
ba.IndexType = TypeID(bo.Uint32(b[4:]))
ba.Nelems = bo.Uint32(b[8:])
return btfArrayLen, nil
}

type btfMember struct {
NameOff uint32
Type TypeID
Offset uint32
}

var btfMemberLen = packedSize[btfMember]()

func unmarshalBtfMembers(members []btfMember, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range members {
if off+btfMemberLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfMember %d", i)
}

members[i].NameOff = bo.Uint32(b[off+0:])
members[i].Type = TypeID(bo.Uint32(b[off+4:]))
members[i].Offset = bo.Uint32(b[off+8:])

off += btfMemberLen
}

return off, nil
}

type btfVarSecinfo struct {
Type TypeID
Offset uint32
Size uint32
}

var btfVarSecinfoLen = packedSize[btfVarSecinfo]()

func unmarshalBtfVarSecInfos(secinfos []btfVarSecinfo, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range secinfos {
if off+btfVarSecinfoLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfVarSecinfo %d", i)
}

secinfos[i].Type = TypeID(bo.Uint32(b[off+0:]))
secinfos[i].Offset = bo.Uint32(b[off+4:])
secinfos[i].Size = bo.Uint32(b[off+8:])

off += btfVarSecinfoLen
}

return off, nil
}

type btfVariable struct {
Linkage uint32
}

var btfVariableLen = packedSize[btfVariable]()

func unmarshalBtfVariable(bv *btfVariable, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfVariableLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfVariable")
}

bv.Linkage = bo.Uint32(b[0:])
return btfVariableLen, nil
}

type btfEnum struct {
NameOff uint32
Val uint32
}

var btfEnumLen = packedSize[btfEnum]()

func unmarshalBtfEnums(enums []btfEnum, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range enums {
if off+btfEnumLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum %d", i)
}

enums[i].NameOff = bo.Uint32(b[off+0:])
enums[i].Val = bo.Uint32(b[off+4:])

off += btfEnumLen
}

return off, nil
}

type btfEnum64 struct {
NameOff uint32
ValLo32 uint32
ValHi32 uint32
}

var btfEnum64Len = packedSize[btfEnum64]()

func unmarshalBtfEnums64(enums []btfEnum64, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range enums {
if off+btfEnum64Len > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum64 %d", i)
}

enums[i].NameOff = bo.Uint32(b[off+0:])
enums[i].ValLo32 = bo.Uint32(b[off+4:])
enums[i].ValHi32 = bo.Uint32(b[off+8:])

off += btfEnum64Len
}

return off, nil
}

type btfParam struct {
NameOff uint32
Type TypeID
}

var btfParamLen = packedSize[btfParam]()

func unmarshalBtfParams(params []btfParam, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range params {
if off+btfParamLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfParam %d", i)
}

params[i].NameOff = bo.Uint32(b[off+0:])
params[i].Type = TypeID(bo.Uint32(b[off+4:]))

off += btfParamLen
}

return off, nil
}

type btfDeclTag struct {
ComponentIdx uint32
}

var btfDeclTagLen = packedSize[btfDeclTag]()

func unmarshalBtfDeclTag(bdt *btfDeclTag, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfDeclTagLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfDeclTag")
}

bdt.ComponentIdx = bo.Uint32(b[0:])
return btfDeclTagLen, nil
}
Loading

0 comments on commit 97891cc

Please sign in to comment.