diff --git a/decoder/decoder.go b/decoder/decoder.go index 3c8cc157..15837738 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -23,6 +23,7 @@ type Decoder interface { type Set struct { mu sync.Mutex decoders map[string]Decoder + cache map[string][]string } // NewSet creates a Set with all known decoders @@ -56,6 +57,7 @@ func NewSet() (*Set, error) { "syscall": &Syscall{}, "uint": &UInt{}, }, + cache: map[string][]string{}, }, nil } @@ -87,6 +89,24 @@ func (s *Set) Decode(in []byte, label config.Label) ([]byte, error) { // DecodeLabels transforms eBPF map key bytes into a list of label values // according to configuration func (s *Set) DecodeLabels(in []byte, labels []config.Label) ([]string, error) { + // string(in) must not be a variable to avoid allocation: + // * https://github.com/golang/go/commit/f5f5a8b6209f8 + if cached, ok := s.cache[string(in)]; ok { + return cached, nil + } + + values, err := s.decodeLabels(in, labels) + if err != nil { + return nil, err + } + + s.cache[string(in)] = values + + return values, nil +} + +// decodeLabels is the inner function of DecodeLabels without any caching +func (s *Set) decodeLabels(in []byte, labels []config.Label) ([]string, error) { values := make([]string, len(labels)) off := uint(0) diff --git a/decoder/decoder_test.go b/decoder/decoder_test.go index 646888af..5871f628 100644 --- a/decoder/decoder_test.go +++ b/decoder/decoder_test.go @@ -151,6 +151,77 @@ func TestDecodeLabels(t *testing.T) { } } +func BenchmarkCache(b *testing.B) { + in := []byte{ + 0x8, 0xab, 0xce, 0xef, + 0xde, 0xad, + 0xbe, 0xef, + 0x8, 0xab, 0xce, 0xef, 0x8, 0xab, 0xce, 0xef, + } + + labels := []config.Label{ + { + Name: "number1", + Size: 4, + Decoders: []config.Decoder{ + { + Name: "uint", + }, + }, + }, + { + Name: "number2", + Size: 2, + Decoders: []config.Decoder{ + { + Name: "uint", + }, + }, + }, + { + Name: "number3", + Size: 2, + Decoders: []config.Decoder{ + { + Name: "uint", + }, + }, + }, + { + Name: "number4", + Size: 8, + Decoders: []config.Decoder{ + { + Name: "uint", + }, + }, + }, + } + + s, err := NewSet() + if err != nil { + b.Fatal(err) + } + + b.Run("direct", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := s.decodeLabels(in, labels) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("cached", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := s.DecodeLabels(in, labels) + if err != nil { + b.Fatal(err) + } + } + }) +} + func zeroPaddedString(in string, size int) []byte { if len(in) > size { panic(fmt.Sprintf("string %q is longer than requested size %d", in, size))