mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08: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"
|
"encoding/json"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +34,8 @@ const (
|
||||||
labelSep = '\xfe'
|
labelSep = '\xfe'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var seps = []byte{'\xff'}
|
||||||
|
|
||||||
// Label is a key/value pair of strings.
|
// Label is a key/value pair of strings.
|
||||||
type Label struct {
|
type Label struct {
|
||||||
Name, Value string
|
Name, Value string
|
||||||
|
@ -132,11 +135,44 @@ func (ls Labels) MatchLabels(on bool, names ...string) Labels {
|
||||||
return matchedLabels
|
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.
|
// Hash returns a hash value for the label set.
|
||||||
func (ls Labels) Hash() uint64 {
|
func (ls Labels) Hash() uint64 {
|
||||||
b := make([]byte, 0, 1024)
|
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, v.Name...)
|
||||||
b = append(b, sep)
|
b = append(b, sep)
|
||||||
b = append(b, v.Value...)
|
b = append(b, v.Value...)
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
package labels
|
package labels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/util/testutil"
|
"github.com/prometheus/prometheus/util/testutil"
|
||||||
|
@ -629,3 +631,68 @@ func TestBuilder_Labels(t *testing.T) {
|
||||||
}).Labels(),
|
}).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