mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Merge pull request #510 from grafana/cherrypick-stringlabels
Cherrypick stringlabels optimisations
This commit is contained in:
commit
3a61decb3d
|
@ -273,13 +273,27 @@ func (ls Labels) Copy() Labels {
|
|||
// Get returns the value for the label with the given name.
|
||||
// Returns an empty string if the label doesn't exist.
|
||||
func (ls Labels) Get(name string) string {
|
||||
if name == "" { // Avoid crash in loop if someone asks for "".
|
||||
return "" // Prometheus does not store blank label names.
|
||||
}
|
||||
for i := 0; i < len(ls.data); {
|
||||
var lName, lValue string
|
||||
lName, i = decodeString(ls.data, i)
|
||||
lValue, i = decodeString(ls.data, i)
|
||||
if lName == name {
|
||||
return lValue
|
||||
var size int
|
||||
size, i = decodeSize(ls.data, i)
|
||||
if ls.data[i] == name[0] {
|
||||
lName := ls.data[i : i+size]
|
||||
i += size
|
||||
if lName == name {
|
||||
lValue, _ := decodeString(ls.data, i)
|
||||
return lValue
|
||||
}
|
||||
} else {
|
||||
if ls.data[i] > name[0] { // Stop looking if we've gone past.
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
size, i = decodeSize(ls.data, i)
|
||||
i += size
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -422,37 +436,49 @@ func FromStrings(ss ...string) Labels {
|
|||
|
||||
// Compare compares the two label sets.
|
||||
// The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
|
||||
// TODO: replace with Less function - Compare is never needed.
|
||||
// TODO: just compare the underlying strings when we don't need alphanumeric sorting.
|
||||
func Compare(a, b Labels) int {
|
||||
l := len(a.data)
|
||||
if len(b.data) < l {
|
||||
l = len(b.data)
|
||||
// Find the first byte in the string where a and b differ.
|
||||
shorter, longer := a.data, b.data
|
||||
if len(b.data) < len(a.data) {
|
||||
shorter, longer = b.data, a.data
|
||||
}
|
||||
i := 0
|
||||
// First, go 8 bytes at a time. Data strings are expected to be 8-byte aligned.
|
||||
sp := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&shorter)).Data)
|
||||
lp := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&longer)).Data)
|
||||
for ; i < len(shorter)-8; i += 8 {
|
||||
if *(*uint64)(unsafe.Add(sp, i)) != *(*uint64)(unsafe.Add(lp, i)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Now go 1 byte at a time.
|
||||
for ; i < len(shorter); i++ {
|
||||
if shorter[i] != longer[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(shorter) {
|
||||
// One Labels was a prefix of the other; the set with fewer labels compares lower.
|
||||
return len(a.data) - len(b.data)
|
||||
}
|
||||
|
||||
ia, ib := 0, 0
|
||||
for ia < l {
|
||||
var aName, bName string
|
||||
aName, ia = decodeString(a.data, ia)
|
||||
bName, ib = decodeString(b.data, ib)
|
||||
if aName != bName {
|
||||
if aName < bName {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
var aValue, bValue string
|
||||
aValue, ia = decodeString(a.data, ia)
|
||||
bValue, ib = decodeString(b.data, ib)
|
||||
if aValue != bValue {
|
||||
if aValue < bValue {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
// Now we know that there is some difference before the end of a and b.
|
||||
// Go back through the fields and find which field that difference is in.
|
||||
firstCharDifferent := i
|
||||
for i = 0; ; {
|
||||
size, nextI := decodeSize(a.data, i)
|
||||
if nextI+size > firstCharDifferent {
|
||||
break
|
||||
}
|
||||
i = nextI + size
|
||||
}
|
||||
// If all labels so far were in common, the set with fewer labels comes first.
|
||||
return len(a.data) - len(b.data)
|
||||
// Difference is inside this entry.
|
||||
aStr, _ := decodeString(a.data, i)
|
||||
bStr, _ := decodeString(b.data, i)
|
||||
if aStr < bStr {
|
||||
return -1
|
||||
}
|
||||
return +1
|
||||
}
|
||||
|
||||
// Copy labels from b on top of whatever was in ls previously, reusing memory or expanding if needed.
|
||||
|
|
|
@ -361,6 +361,18 @@ func TestLabels_Compare(t *testing.T) {
|
|||
"bbc", "222"),
|
||||
expected: -1,
|
||||
},
|
||||
{
|
||||
compared: FromStrings(
|
||||
"aaa", "111",
|
||||
"bb", "222"),
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
compared: FromStrings(
|
||||
"aaa", "111",
|
||||
"bbbb", "222"),
|
||||
expected: -1,
|
||||
},
|
||||
{
|
||||
compared: FromStrings(
|
||||
"aaa", "111"),
|
||||
|
@ -380,6 +392,10 @@ func TestLabels_Compare(t *testing.T) {
|
|||
"bbb", "222"),
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
compared: EmptyLabels(),
|
||||
expected: 1,
|
||||
},
|
||||
}
|
||||
|
||||
sign := func(a int) int {
|
||||
|
@ -395,6 +411,8 @@ func TestLabels_Compare(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
got := Compare(labels, test.compared)
|
||||
require.Equal(t, sign(test.expected), sign(got), "unexpected comparison result for test case %d", i)
|
||||
got = Compare(test.compared, labels)
|
||||
require.Equal(t, -sign(test.expected), sign(got), "unexpected comparison result for reverse test case %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,7 +443,8 @@ func TestLabels_Has(t *testing.T) {
|
|||
|
||||
func TestLabels_Get(t *testing.T) {
|
||||
require.Equal(t, "", FromStrings("aaa", "111", "bbb", "222").Get("foo"))
|
||||
require.Equal(t, "111", FromStrings("aaa", "111", "bbb", "222").Get("aaa"))
|
||||
require.Equal(t, "111", FromStrings("aaaa", "111", "bbb", "222").Get("aaaa"))
|
||||
require.Equal(t, "222", FromStrings("aaaa", "111", "bbb", "222").Get("bbb"))
|
||||
}
|
||||
|
||||
// BenchmarkLabels_Get was written to check whether a binary search can improve the performance vs the linear search implementation
|
||||
|
@ -445,7 +464,7 @@ func BenchmarkLabels_Get(b *testing.B) {
|
|||
maxLabels := 30
|
||||
allLabels := make([]Label, maxLabels)
|
||||
for i := 0; i < maxLabels; i++ {
|
||||
allLabels[i] = Label{Name: strings.Repeat(string('a'+byte(i)), 5)}
|
||||
allLabels[i] = Label{Name: strings.Repeat(string('a'+byte(i)), 5+(i%5))}
|
||||
}
|
||||
for _, size := range []int{5, 10, maxLabels} {
|
||||
b.Run(fmt.Sprintf("with %d labels", size), func(b *testing.B) {
|
||||
|
@ -456,6 +475,7 @@ func BenchmarkLabels_Get(b *testing.B) {
|
|||
{"get first label", allLabels[0].Name},
|
||||
{"get middle label", allLabels[size/2].Name},
|
||||
{"get last label", allLabels[size-1].Name},
|
||||
{"get not-found label", "benchmark"},
|
||||
} {
|
||||
b.Run(scenario.desc, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
@ -468,27 +488,34 @@ func BenchmarkLabels_Get(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
var comparisonBenchmarkScenarios = []struct {
|
||||
desc string
|
||||
base, other Labels
|
||||
}{
|
||||
{
|
||||
"equal",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
},
|
||||
{
|
||||
"not equal",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "a_different_label_value"),
|
||||
},
|
||||
{
|
||||
"different sizes",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value"),
|
||||
},
|
||||
{
|
||||
"lots",
|
||||
FromStrings("aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo", "ppp", "qqq", "rrz"),
|
||||
FromStrings("aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo", "ppp", "qqq", "rrr"),
|
||||
},
|
||||
}
|
||||
|
||||
func BenchmarkLabels_Equals(b *testing.B) {
|
||||
for _, scenario := range []struct {
|
||||
desc string
|
||||
base, other Labels
|
||||
}{
|
||||
{
|
||||
"equal",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
},
|
||||
{
|
||||
"not equal",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "a_different_label_value"),
|
||||
},
|
||||
{
|
||||
"different sizes",
|
||||
FromStrings("a_label_name", "a_label_value", "another_label_name", "another_label_value"),
|
||||
FromStrings("a_label_name", "a_label_value"),
|
||||
},
|
||||
} {
|
||||
for _, scenario := range comparisonBenchmarkScenarios {
|
||||
b.Run(scenario.desc, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -498,6 +525,17 @@ func BenchmarkLabels_Equals(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkLabels_Compare(b *testing.B) {
|
||||
for _, scenario := range comparisonBenchmarkScenarios {
|
||||
b.Run(scenario.desc, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Compare(scenario.base, scenario.other)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabels_Copy(t *testing.T) {
|
||||
require.Equal(t, FromStrings("aaa", "111", "bbb", "222"), FromStrings("aaa", "111", "bbb", "222").Copy())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue