Skip to content

Commit

Permalink
[coreinternal/pdatautil] Reduce allocations in Map/ValueHash functions (
Browse files Browse the repository at this point in the history
#17827)

[coreinternal/pdatautil] Reduce number of allocations in hash functions
  • Loading branch information
dmitryax authored Jan 17, 2023
1 parent e857f00 commit 956d956
Showing 1 changed file with 72 additions and 43 deletions.
115 changes: 72 additions & 43 deletions internal/coreinternal/pdatautil/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"hash"
"math"
"sort"
"sync"

"github.com/cespare/xxhash/v2"
"go.opentelemetry.io/collector/pdata/pcommon"
Expand All @@ -40,87 +41,115 @@ var (
valSliceSuffix = []byte{'\xff'}
)

type hashWriter struct {
h hash.Hash
strBuf []byte
keysBuf []string
sumHash []byte
numBuf []byte
}

func newHashWriter() *hashWriter {
return &hashWriter{
h: xxhash.New(),
strBuf: make([]byte, 0, 128),
keysBuf: make([]string, 0, 16),
sumHash: make([]byte, 0, 16),
numBuf: make([]byte, 8),
}
}

var hashWriterPool = &sync.Pool{
New: func() interface{} { return newHashWriter() },
}

// MapHash return a hash for the provided map.
// Maps with the same underlying key/value pairs in different order produce the same deterministic hash value.
func MapHash(m pcommon.Map) [16]byte {
h := xxhash.New()
writeMapHash(h, m)
return hashSum128(h)
hw := hashWriterPool.Get().(*hashWriter)
defer hashWriterPool.Put(hw)
hw.h.Reset()
hw.writeMapHash(m)
return hw.hashSum128()
}

// ValueHash return a hash for the provided pcommon.Value.
func ValueHash(v pcommon.Value) [16]byte {
h := xxhash.New()
writeValueHash(h, v)
return hashSum128(h)
hw := hashWriterPool.Get().(*hashWriter)
defer hashWriterPool.Put(hw)
hw.h.Reset()
hw.writeValueHash(v)
return hw.hashSum128()
}

func writeMapHash(h hash.Hash, m pcommon.Map) {
keys := make([]string, 0, m.Len())
func (hw *hashWriter) writeMapHash(m pcommon.Map) {
hw.keysBuf = hw.keysBuf[:0]
m.Range(func(k string, v pcommon.Value) bool {
keys = append(keys, k)
hw.keysBuf = append(hw.keysBuf, k)
return true
})
sort.Strings(keys)
for _, k := range keys {
sort.Strings(hw.keysBuf)
for _, k := range hw.keysBuf {
v, _ := m.Get(k)
h.Write(keyPrefix)
h.Write([]byte(k))
writeValueHash(h, v)
hw.strBuf = hw.strBuf[:0]
hw.strBuf = append(hw.strBuf, keyPrefix...)
hw.strBuf = append(hw.strBuf, k...)
hw.h.Write(hw.strBuf)
hw.writeValueHash(v)
}
}

func writeSliceHash(h hash.Hash, sl pcommon.Slice) {
func (hw *hashWriter) writeSliceHash(sl pcommon.Slice) {
for i := 0; i < sl.Len(); i++ {
writeValueHash(h, sl.At(i))
hw.writeValueHash(sl.At(i))
}
}

func writeValueHash(h hash.Hash, v pcommon.Value) {
func (hw *hashWriter) writeValueHash(v pcommon.Value) {
switch v.Type() {
case pcommon.ValueTypeStr:
h.Write(valStrPrefix)
h.Write([]byte(v.Str()))
hw.strBuf = hw.strBuf[:0]
hw.strBuf = append(hw.strBuf, valStrPrefix...)
hw.strBuf = append(hw.strBuf, v.Str()...)
hw.h.Write(hw.strBuf)
case pcommon.ValueTypeBool:
if v.Bool() {
h.Write(valBoolTrue)
hw.h.Write(valBoolTrue)
} else {
h.Write(valBoolFalse)
hw.h.Write(valBoolFalse)
}
case pcommon.ValueTypeInt:
h.Write(valIntPrefix)
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(v.Int()))
h.Write(b)
hw.h.Write(valIntPrefix)
binary.LittleEndian.PutUint64(hw.numBuf, uint64(v.Int()))
hw.h.Write(hw.numBuf)
case pcommon.ValueTypeDouble:
h.Write(valDoublePrefix)
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, math.Float64bits(v.Double()))
h.Write(b)
hw.h.Write(valDoublePrefix)
binary.LittleEndian.PutUint64(hw.numBuf, math.Float64bits(v.Double()))
hw.h.Write(hw.numBuf)
case pcommon.ValueTypeMap:
h.Write(valMapPrefix)
writeMapHash(h, v.Map())
h.Write(valMapSuffix)
hw.h.Write(valMapPrefix)
hw.writeMapHash(v.Map())
hw.h.Write(valMapSuffix)
case pcommon.ValueTypeSlice:
h.Write(valSlicePrefix)
writeSliceHash(h, v.Slice())
h.Write(valSliceSuffix)
hw.h.Write(valSlicePrefix)
hw.writeSliceHash(v.Slice())
hw.h.Write(valSliceSuffix)
case pcommon.ValueTypeBytes:
h.Write(valBytesPrefix)
h.Write(v.Bytes().AsRaw())
hw.h.Write(valBytesPrefix)
hw.h.Write(v.Bytes().AsRaw())
case pcommon.ValueTypeEmpty:
h.Write(valEmpty)
hw.h.Write(valEmpty)
}
}

// hashSum128 returns a [16]byte hash sum.
func hashSum128(h hash.Hash) [16]byte {
b := make([]byte, 0, 16)
b = h.Sum(b)
func (hw *hashWriter) hashSum128() [16]byte {
b := hw.sumHash[:0]
b = hw.h.Sum(b)

// Append an extra byte to generate another part of the hash sum
_, _ = h.Write(extraByte)
b = h.Sum(b)
_, _ = hw.h.Write(extraByte)
b = hw.h.Sum(b)

res := [16]byte{}
copy(res[:], b)
Expand Down

0 comments on commit 956d956

Please sign in to comment.