Skip to content

Commit fe55572

Browse files
committed
Adds benchmark to compare the new unique algorithm
Signed-off-by: Cyril Tovena <cyril.tovena@gmail.com>
1 parent 0e8170a commit fe55572

File tree

6 files changed

+109
-35
lines changed

6 files changed

+109
-35
lines changed

pkg/chunk/chunk_store.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,15 @@ func (c *store) LabelValuesForMetricName(ctx context.Context, userID string, fro
204204
return nil, err
205205
}
206206

207-
var result []string
208-
values := map[string]struct{}{}
207+
var result UniqueStrings
209208
for _, entry := range entries {
210209
_, labelValue, _, _, err := parseChunkTimeRangeValue(entry.RangeValue, entry.Value)
211210
if err != nil {
212211
return nil, err
213212
}
214-
result = appendUniqueStrings(result, values, string(labelValue))
213+
result.Add(string(labelValue))
215214
}
216-
sort.Strings(result)
217-
return result, nil
215+
return result.Strings(), nil
218216
}
219217

220218
// LabelNamesForMetricName retrieves all label names for a metric name.
@@ -462,7 +460,6 @@ func (c *store) lookupEntriesByQueries(ctx context.Context, queries []IndexQuery
462460

463461
func (c *store) parseIndexEntries(ctx context.Context, entries []IndexEntry, matcher *labels.Matcher) ([]string, error) {
464462
result := make([]string, 0, len(entries))
465-
values := map[string]struct{}{}
466463
for _, entry := range entries {
467464
chunkKey, labelValue, _, _, err := parseChunkTimeRangeValue(entry.RangeValue, entry.Value)
468465
if err != nil {
@@ -472,10 +469,11 @@ func (c *store) parseIndexEntries(ctx context.Context, entries []IndexEntry, mat
472469
if matcher != nil && !matcher.Matches(string(labelValue)) {
473470
continue
474471
}
475-
result = appendUniqueStrings(result, values, chunkKey)
472+
result = append(result, chunkKey)
476473
}
477474
// Return ids sorted and deduped because they will be merged with other sets.
478475
sort.Strings(result)
476+
result = uniqueStrings(result)
479477
return result, nil
480478
}
481479

pkg/chunk/chunk_store_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,3 +851,49 @@ func TestStoreMaxLookBack(t *testing.T) {
851851
require.Equal(t, 1, len(chunks))
852852
chunks[0].Through.Equal(now)
853853
}
854+
855+
func benchmarkParseIndexEntries(i int64, b *testing.B) {
856+
b.ReportAllocs()
857+
b.StopTimer()
858+
store := &store{}
859+
ctx := context.Background()
860+
entries := generateIndexEntries(i)
861+
matcher, err := labels.NewMatcher(labels.MatchRegexp, "", ".*")
862+
if err != nil {
863+
b.Fatal(err)
864+
}
865+
b.StartTimer()
866+
for n := 0; n < b.N; n++ {
867+
keys, err := store.parseIndexEntries(ctx, entries, matcher)
868+
if err != nil {
869+
b.Fatal(err)
870+
}
871+
if len(keys) != len(entries)/2 {
872+
b.Fatalf("expected keys:%d got:%d", len(entries)/2, len(keys))
873+
}
874+
}
875+
}
876+
877+
func BenchmarkParseIndexEntries500(b *testing.B) { benchmarkParseIndexEntries(500, b) }
878+
func BenchmarkParseIndexEntries2500(b *testing.B) { benchmarkParseIndexEntries(2500, b) }
879+
func BenchmarkParseIndexEntries10000(b *testing.B) { benchmarkParseIndexEntries(10000, b) }
880+
func BenchmarkParseIndexEntries50000(b *testing.B) { benchmarkParseIndexEntries(50000, b) }
881+
882+
func generateIndexEntries(n int64) []IndexEntry {
883+
res := make([]IndexEntry, 0, n)
884+
for i := int64(n - 1); i >= 0; i-- {
885+
labelValue := fmt.Sprintf("labelvalue%d", i%(n/2))
886+
chunkID := fmt.Sprintf("chunkid%d", i%(n/2))
887+
rangeValue := []byte{}
888+
rangeValue = append(rangeValue, []byte("component1")...)
889+
rangeValue = append(rangeValue, 0)
890+
rangeValue = append(rangeValue, []byte(labelValue)...)
891+
rangeValue = append(rangeValue, 0)
892+
rangeValue = append(rangeValue, []byte(chunkID)...)
893+
rangeValue = append(rangeValue, 0)
894+
res = append(res, IndexEntry{
895+
RangeValue: rangeValue,
896+
})
897+
}
898+
return res
899+
}

pkg/chunk/chunk_store_utils.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package chunk
22

33
import (
44
"context"
5-
"sort"
65
"sync"
76

87
"github.com/go-kit/kit/log/level"
@@ -38,15 +37,13 @@ func keysFromChunks(chunks []Chunk) []string {
3837
}
3938

4039
func labelNamesFromChunks(chunks []Chunk) []string {
41-
keys := map[string]struct{}{}
42-
var result []string
40+
var result UniqueStrings
4341
for _, c := range chunks {
4442
for _, l := range c.Metric {
45-
result = appendUniqueStrings(result, keys, string(l.Name))
43+
result.Add(string(l.Name))
4644
}
4745
}
48-
sort.Strings(result)
49-
return result
46+
return result.Strings()
5047
}
5148

5249
func filterChunksByUniqueFingerprint(chunks []Chunk) ([]Chunk, []string) {

pkg/chunk/composite_store.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,30 @@ func (c compositeStore) Get(ctx context.Context, userID string, from, through mo
100100

101101
// LabelValuesForMetricName retrieves all label values for a single label name and metric name.
102102
func (c compositeStore) LabelValuesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string, labelName string) ([]string, error) {
103-
var result []string
104-
uniqueValues := map[string]struct{}{}
103+
var result UniqueStrings
105104
err := c.forStores(from, through, func(from, through model.Time, store Store) error {
106105
labelValues, err := store.LabelValuesForMetricName(ctx, userID, from, through, metricName, labelName)
107106
if err != nil {
108107
return err
109108
}
110-
result = appendUniqueStrings(result, uniqueValues, labelValues...)
109+
result.Add(labelValues...)
111110
return nil
112111
})
113-
sort.Strings(result)
114-
return result, err
112+
return result.Strings(), err
115113
}
116114

117115
// LabelNamesForMetricName retrieves all label names for a metric name.
118116
func (c compositeStore) LabelNamesForMetricName(ctx context.Context, userID string, from, through model.Time, metricName string) ([]string, error) {
119-
var result []string
120-
uniqueValues := map[string]struct{}{}
117+
var result UniqueStrings
121118
err := c.forStores(from, through, func(from, through model.Time, store Store) error {
122119
labelNames, err := store.LabelNamesForMetricName(ctx, userID, from, through, metricName)
123120
if err != nil {
124121
return err
125122
}
126-
result = appendUniqueStrings(result, uniqueValues, labelNames...)
123+
result.Add(labelNames...)
127124
return nil
128125
})
129-
sort.Strings(result)
130-
return result, err
126+
return result.Strings(), err
131127
}
132128

133129
func (c compositeStore) GetChunkRefs(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([][]Chunk, []*Fetcher, error) {

pkg/chunk/series_store.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7-
"sort"
87

98
"github.com/go-kit/kit/log/level"
109
jsoniter "github.com/json-iterator/go"
@@ -406,18 +405,18 @@ func (c *seriesStore) lookupLabelNamesBySeries(ctx context.Context, from, throug
406405
return nil, err
407406
}
408407
level.Debug(log).Log("entries", len(entries))
409-
result := []string{model.MetricNameLabel}
410-
uniqueLabelNames := map[string]struct{}{model.MetricNameLabel: {}}
408+
409+
var result UniqueStrings
410+
result.Add(model.MetricNameLabel)
411411
for _, entry := range entries {
412412
lbs := []string{}
413413
err := jsoniter.ConfigFastest.Unmarshal(entry.Value, &lbs)
414414
if err != nil {
415415
return nil, err
416416
}
417-
result = appendUniqueStrings(result, uniqueLabelNames, lbs...)
417+
result.Add(lbs...)
418418
}
419-
sort.Strings(result)
420-
return result, nil
419+
return result.Strings(), nil
421420
}
422421

423422
// Put implements ChunkStore

pkg/chunk/strings.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
package chunk
22

3+
import "sort"
4+
5+
func uniqueStrings(cs []string) []string {
6+
if len(cs) == 0 {
7+
return []string{}
8+
}
9+
10+
result := make([]string, 1, len(cs))
11+
result[0] = cs[0]
12+
i, j := 0, 1
13+
for j < len(cs) {
14+
if result[i] == cs[j] {
15+
j++
16+
continue
17+
}
18+
result = append(result, cs[j])
19+
i++
20+
j++
21+
}
22+
return result
23+
}
24+
325
func intersectStrings(left, right []string) []string {
426
var (
527
i, j = 0, 0
@@ -38,12 +60,28 @@ func nWayIntersectStrings(sets [][]string) []string {
3860
}
3961
}
4062

41-
func appendUniqueStrings(result []string, uniqueValues map[string]struct{}, values ...string) []string {
42-
for _, l := range values {
43-
if _, ok := uniqueValues[l]; !ok {
44-
uniqueValues[l] = struct{}{}
45-
result = append(result, l)
63+
// UniqueStrings keeps a slice of unique strings.
64+
type UniqueStrings struct {
65+
values map[string]struct{}
66+
result []string
67+
}
68+
69+
// Add adds a new string, dropping duplicates.
70+
func (us *UniqueStrings) Add(strings ...string) {
71+
for _, s := range strings {
72+
if _, ok := us.values[s]; ok {
73+
continue
4674
}
75+
if us.values == nil {
76+
us.values = map[string]struct{}{}
77+
}
78+
us.values[s] = struct{}{}
79+
us.result = append(us.result, s)
4780
}
48-
return result
81+
}
82+
83+
// Strings returns the sorted sliced of unique strings.
84+
func (us UniqueStrings) Strings() []string {
85+
sort.Strings(us.result)
86+
return us.result
4987
}

0 commit comments

Comments
 (0)