Skip to content

Commit

Permalink
Use string([]byte) as a cache key in decoder
Browse files Browse the repository at this point in the history
Go has a special case for `string([]byte)` that doesn't allocate:

* golang/go@f5f5a8b6209f8

Before and after:

```
BenchmarkCache/cached-10   132218238   9.292 ns/op       0 B/op       0 allocs/op
BenchmarkCache/cached-10   212642139   5.694 ns/op       0 B/op       0 allocs/op
```
  • Loading branch information
bobrik committed Feb 5, 2024
1 parent 5ed2045 commit b209828
Showing 1 changed file with 7 additions and 18 deletions.
25 changes: 7 additions & 18 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package decoder

import (
"bytes"
"errors"
"fmt"
"hash/maphash"
"sync"

"github.com/cloudflare/ebpf_exporter/v2/config"
Expand All @@ -14,11 +12,6 @@ import (
// ErrSkipLabelSet instructs exporter to skip label set
var ErrSkipLabelSet = errors.New("this label set should be skipped")

type cacheValue struct {
in []byte
out []string
}

// Decoder transforms byte field value into a byte value representing string
// to either use as an input for another Decoder or to use as the final
// label value for Prometheus metrics
Expand All @@ -30,8 +23,7 @@ type Decoder interface {
type Set struct {
mu sync.Mutex
decoders map[string]Decoder
cache map[uint64]cacheValue
seed maphash.Seed
cache map[string][]string
}

// NewSet creates a Set with all known decoders
Expand Down Expand Up @@ -65,8 +57,7 @@ func NewSet() (*Set, error) {
"syscall": &Syscall{},
"uint": &UInt{},
},
cache: map[uint64]cacheValue{},
seed: maphash.MakeSeed(),
cache: map[string][]string{},
}, nil
}

Expand Down Expand Up @@ -98,20 +89,18 @@ 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) {
cacheKey := maphash.Bytes(s.seed, in)
if cached, ok := s.cache[cacheKey]; ok {
// Make sure that there is no collision here
if bytes.Equal(cached.in, in) {
return cached.out, nil
}
// 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[cacheKey] = cacheValue{in: in, out: values}
s.cache[string(in)] = values

return values, nil
}
Expand Down

0 comments on commit b209828

Please sign in to comment.