diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go index 7defc6abc..2d79dbe75 100644 --- a/pkg/labels/labels.go +++ b/pkg/labels/labels.go @@ -131,44 +131,46 @@ func (ls Labels) Hash() uint64 { } // HashForLabels returns a hash value for the labels matching the provided names. -func (ls Labels) HashForLabels(names ...string) uint64 { - b := make([]byte, 0, 1024) - - for _, v := range ls { - for _, n := range names { - if v.Name == n { - b = append(b, v.Name...) - b = append(b, sep) - b = append(b, v.Value...) - b = append(b, sep) - break - } +// 'names' have to be sorted in ascending order. +func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) { + b = b[:0] + i, j := 0, 0 + for i < len(ls) && j < len(names) { + if names[j] < ls[i].Name { + j++ + } else if ls[i].Name < names[j] { + i++ + } else { + b = append(b, ls[i].Name...) + b = append(b, sep) + b = append(b, ls[i].Value...) + b = append(b, sep) + i++ + j++ } } - return xxhash.Sum64(b) + return xxhash.Sum64(b), b } // HashWithoutLabels returns a hash value for all labels except those matching // the provided names. -func (ls Labels) HashWithoutLabels(names ...string) uint64 { - b := make([]byte, 0, 1024) - -Outer: - for _, v := range ls { - if v.Name == MetricName { +// 'names' have to be sorted in ascending order. +func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) { + b = b[:0] + j := 0 + for i := range ls { + for j < len(names) && names[j] < ls[i].Name { + j++ + } + if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) { continue } - for _, n := range names { - if v.Name == n { - continue Outer - } - } - b = append(b, v.Name...) + b = append(b, ls[i].Name...) b = append(b, sep) - b = append(b, v.Value...) + b = append(b, ls[i].Value...) b = append(b, sep) } - return xxhash.Sum64(b) + return xxhash.Sum64(b), b } // Copy returns a copy of the labels. diff --git a/promql/engine.go b/promql/engine.go index ad40ff80a..ebba3eaf9 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1523,13 +1523,17 @@ func (ev *evaluator) VectorBinop(op ItemType, lhs, rhs Vector, matching *VectorM // signatureFunc returns a function that calculates the signature for a metric // ignoring the provided labels. If on, then the given labels are only used instead. func signatureFunc(on bool, names ...string) func(labels.Labels) uint64 { - // TODO(fabxc): ensure names are sorted and then use that and sortedness - // of labels by names to speed up the operations below. - // Alternatively, inline the hashing and don't build new label sets. + sort.Strings(names) if on { - return func(lset labels.Labels) uint64 { return lset.HashForLabels(names...) } + return func(lset labels.Labels) uint64 { + h, _ := lset.HashForLabels(make([]byte, 0, 1024), names...) + return h + } + } + return func(lset labels.Labels) uint64 { + h, _ := lset.HashWithoutLabels(make([]byte, 0, 1024), names...) + return h } - return func(lset labels.Labels) uint64 { return lset.HashWithoutLabels(names...) } } // resultMetric returns the metric for the given sample(s) based on the Vector @@ -1722,8 +1726,9 @@ func (ev *evaluator) aggregation(op ItemType, grouping []string, without bool, p } } + sort.Strings(grouping) lb := labels.NewBuilder(nil) - + buf := make([]byte, 0, 1024) for _, s := range vec { metric := s.Metric @@ -1737,9 +1742,9 @@ func (ev *evaluator) aggregation(op ItemType, grouping []string, without bool, p groupingKey uint64 ) if without { - groupingKey = metric.HashWithoutLabels(grouping...) + groupingKey, buf = metric.HashWithoutLabels(buf, grouping...) } else { - groupingKey = metric.HashForLabels(grouping...) + groupingKey, buf = metric.HashForLabels(buf, grouping...) } group, ok := result[groupingKey]