labels: improve Get method for stringlabels build (#12485)

Inline one call to `decodeString`, and skip decoding the value string
until we find a match for the name.
Do a quick check on the first character in each string,
and exit early if we've gone past - labels are sorted in order.

Also improve tests and benchmark:
* labels: test Get with varying lengths - it's not typical for Prometheus labels to all be the same length.
* extend benchmark with label not found

---------

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
This commit is contained in:
Bryan Boreham 2023-06-26 18:35:22 +01:00
parent 19c9db3c07
commit 1e16c60bae
2 changed files with 23 additions and 7 deletions

View file

@ -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 ""
}

View file

@ -443,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
@ -463,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) {
@ -474,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()