mirror of
https://github.com/prometheus/prometheus.git
synced 2024-09-20 07:47:31 -07:00
labels: Reduce allocated memory by Hash method in edge cases; Added tests.
Old: /tmp/___BenchmarkLabels_Hash_in_github_com_prometheus_prometheus_pkg_labels -test.v -test.bench ^\QBenchmarkLabels_Hash\E$ -test.run ^$ goos: linux goarch: amd64 pkg: github.com/prometheus/prometheus/pkg/labels BenchmarkLabels_Hash BenchmarkLabels_Hash/typical_labels_under_1KB BenchmarkLabels_Hash/typical_labels_under_1KB-12 5366161 259 ns/op 0 B/op 0 allocs/op BenchmarkLabels_Hash/bigger_labels_over_1KB BenchmarkLabels_Hash/bigger_labels_over_1KB-12 1700371 767 ns/op 2048 B/op 1 allocs/op BenchmarkLabels_Hash/extremely_large_label_value_10MB BenchmarkLabels_Hash/extremely_large_label_value_10MB-12 356 3743115 ns/op 10523442 B/op 1 allocs/op PASS New: /tmp/___BenchmarkLabels_Hash_in_github_com_prometheus_prometheus_pkg_labels -test.v -test.bench ^\QBenchmarkLabels_Hash\E$ -test.run ^$ goos: linux goarch: amd64 pkg: github.com/prometheus/prometheus/pkg/labels BenchmarkLabels_Hash BenchmarkLabels_Hash/typical_labels_under_1KB BenchmarkLabels_Hash/typical_labels_under_1KB-12 4758883 259 ns/op 0 B/op 0 allocs/op BenchmarkLabels_Hash/bigger_labels_over_1KB BenchmarkLabels_Hash/bigger_labels_over_1KB-12 3324492 357 ns/op 80 B/op 1 allocs/op BenchmarkLabels_Hash/extremely_large_label_value_10MB BenchmarkLabels_Hash/extremely_large_label_value_10MB-12 1087 1083949 ns/op 9734 B/op 1 allocs/op PASS Process finished with exit code 0 Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com>
This commit is contained in:
parent
7e2db3d092
commit
61160463cc
|
@ -18,6 +18,7 @@ import (
|
|||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cespare/xxhash"
|
||||
)
|
||||
|
@ -33,6 +34,8 @@ const (
|
|||
labelSep = '\xfe'
|
||||
)
|
||||
|
||||
var seps = []byte{'\xff'}
|
||||
|
||||
// Label is a key/value pair of strings.
|
||||
type Label struct {
|
||||
Name, Value string
|
||||
|
@ -132,11 +135,44 @@ func (ls Labels) MatchLabels(on bool, names ...string) Labels {
|
|||
return matchedLabels
|
||||
}
|
||||
|
||||
// noescape hides a pointer from escape analysis. noescape is
|
||||
// the identity function but escape analysis doesn't think the
|
||||
// output depends on the input. noescape is inlined and currently
|
||||
// compiles down to zero instructions.
|
||||
// USE CAREFULLY!
|
||||
// This was copied from the runtime; see issues 23382 and 7921.
|
||||
//go:nosplit
|
||||
//go:nocheckptr
|
||||
func noescape(p unsafe.Pointer) unsafe.Pointer {
|
||||
x := uintptr(p)
|
||||
return unsafe.Pointer(x ^ 0) //nolint:staticcheck
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
//go:checkptr
|
||||
func noAllocBytes(buf string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&buf))
|
||||
}
|
||||
|
||||
// Hash returns a hash value for the label set.
|
||||
func (ls Labels) Hash() uint64 {
|
||||
b := make([]byte, 0, 1024)
|
||||
for i, v := range ls {
|
||||
if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
|
||||
// If labels entry is 1KB+ allocate do not allocate whole entry.
|
||||
h := xxhash.New()
|
||||
// This allows b to be still on stack and reused. This is safe as xxhash.x
|
||||
// is never used outside of this function.
|
||||
_, _ = h.Write(*(*[]byte)(noescape(unsafe.Pointer(&b))))
|
||||
for _, v := range ls[i:] {
|
||||
_, _ = h.Write(noAllocBytes(v.Name))
|
||||
_, _ = h.Write(seps)
|
||||
_, _ = h.Write(noAllocBytes(v.Value))
|
||||
_, _ = h.Write(seps)
|
||||
}
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
for _, v := range ls {
|
||||
b = append(b, v.Name...)
|
||||
b = append(b, sep)
|
||||
b = append(b, v.Value...)
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
package labels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
|
@ -629,3 +631,68 @@ func TestBuilder_Labels(t *testing.T) {
|
|||
}).Labels(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestLabels_Hash(t *testing.T) {
|
||||
lbls := Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
{Name: "baz", Value: "qux"},
|
||||
}
|
||||
testutil.Equals(t, lbls.Hash(), lbls.Hash())
|
||||
testutil.Assert(t, lbls.Hash() != Labels{lbls[1], lbls[0]}.Hash(), "unordered labels match.")
|
||||
testutil.Assert(t, lbls.Hash() != Labels{lbls[0]}.Hash(), "different labels match.")
|
||||
}
|
||||
|
||||
var benchmarkLabelsResult uint64
|
||||
|
||||
func BenchmarkLabels_Hash(b *testing.B) {
|
||||
for _, tcase := range []struct {
|
||||
name string
|
||||
lbls Labels
|
||||
}{
|
||||
{
|
||||
name: "typical labels under 1KB",
|
||||
lbls: func() Labels {
|
||||
lbls := make(Labels, 10)
|
||||
for i := 0; i < len(lbls); i++ {
|
||||
// Label ~20B name, 50B value.
|
||||
lbls[i] = Label{Name: fmt.Sprintf("abcdefghijabcdefghijabcdefghij%d", i), Value: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i)}
|
||||
}
|
||||
return lbls
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "bigger labels over 1KB",
|
||||
lbls: func() Labels {
|
||||
lbls := make(Labels, 10)
|
||||
for i := 0; i < len(lbls); i++ {
|
||||
//Label ~50B name, 50B value.
|
||||
lbls[i] = Label{Name: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i), Value: fmt.Sprintf("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij%d", i)}
|
||||
}
|
||||
return lbls
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "extremely large label value 10MB",
|
||||
lbls: func() Labels {
|
||||
lbl := &strings.Builder{}
|
||||
lbl.Grow(1024 * 1024 * 10) // 10MB.
|
||||
word := "abcdefghij"
|
||||
for i := 0; i < lbl.Cap()/len(word); i++ {
|
||||
_, _ = lbl.WriteString(word)
|
||||
}
|
||||
return Labels{{Name: "__name__", Value: lbl.String()}}
|
||||
}(),
|
||||
},
|
||||
} {
|
||||
b.Run(tcase.name, func(b *testing.B) {
|
||||
var h uint64
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h = tcase.lbls.Hash()
|
||||
}
|
||||
benchmarkLabelsResult = h
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue