mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
The optimization touches 2 things: 1. Reduces amount of allocations when comparing canonical metric names between left and right parts of expressions. 2. Adds fast path for cases when right part of expression returns scalar: `series_selector or on() vector(1)`, which is a typical expression. ``` benchcmp old.txt new.txt benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat benchmark old ns/op new ns/op delta BenchmarkBinaryOpOr/tss:1_or_tss:1-14 291 272 -6.56% BenchmarkBinaryOpOr/tss:1_or_tss:1000-14 44590 28592 -35.88% BenchmarkBinaryOpOr/tss:1000_or_tss:1-14 103124 39563 -61.64% BenchmarkBinaryOpOr/tss:1000_or_tss:1000-14 20386150 1859335 -90.88% BenchmarkBinaryOpOr/tss:1000_or_on()_vector(0)-14 91382 36805 -59.72% ``` https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8382 ### Checklist The following checks are **mandatory**: - [x] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/). Signed-off-by: hagen1778 <[email protected]>
108 lines
2.5 KiB
Go
108 lines
2.5 KiB
Go
package promql
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/VictoriaMetrics/metricsql"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
|
)
|
|
|
|
func BenchmarkBinaryOpOr(b *testing.B) {
|
|
mustParseMetricsQL := func(s string) *metricsql.BinaryOpExpr {
|
|
e, err := metricsql.Parse(s)
|
|
if err != nil {
|
|
b.Fatalf("cannot parse %q: %s", s, err)
|
|
}
|
|
be, ok := e.(*metricsql.BinaryOpExpr)
|
|
if !ok {
|
|
b.Fatalf("expected BinaryOpExpr from %q: %s", s, err)
|
|
}
|
|
return be
|
|
}
|
|
ts := func(name string) *timeseries {
|
|
mg := []byte(name)
|
|
if len(name) == 0 {
|
|
mg = nil
|
|
}
|
|
return ×eries{
|
|
MetricName: storage.MetricName{
|
|
MetricGroup: mg,
|
|
},
|
|
Timestamps: []int64{1},
|
|
Values: []float64{1},
|
|
}
|
|
}
|
|
|
|
b.Run("tss:1 or tss:1", func(b *testing.B) {
|
|
bfa := &binaryOpFuncArg{
|
|
be: mustParseMetricsQL("a or b"),
|
|
left: []*timeseries{ts("a")},
|
|
right: []*timeseries{ts("b")},
|
|
}
|
|
benchmarkBinaryOpOr(b, bfa)
|
|
})
|
|
b.Run("tss:1 or tss:1000", func(b *testing.B) {
|
|
right := make([]*timeseries, 1000)
|
|
for i := range right {
|
|
right[i] = ts(fmt.Sprintf(`b{foo="%d"}`, i))
|
|
}
|
|
bfa := &binaryOpFuncArg{
|
|
be: mustParseMetricsQL("a or b"),
|
|
left: []*timeseries{ts(`a{foo="0"}`)},
|
|
right: right,
|
|
}
|
|
benchmarkBinaryOpOr(b, bfa)
|
|
})
|
|
b.Run("tss:1000 or tss:1", func(b *testing.B) {
|
|
left := make([]*timeseries, 1000)
|
|
for i := range left {
|
|
left[i] = ts(fmt.Sprintf(`b{foo="%d"}`, i))
|
|
}
|
|
bfa := &binaryOpFuncArg{
|
|
be: mustParseMetricsQL("a or b"),
|
|
left: left,
|
|
right: []*timeseries{ts(`b{foo="0"}`)},
|
|
}
|
|
benchmarkBinaryOpOr(b, bfa)
|
|
})
|
|
b.Run("tss:1000 or tss:1000", func(b *testing.B) {
|
|
left, right := make([]*timeseries, 1000), make([]*timeseries, 1000)
|
|
for i := range left {
|
|
left[i] = ts(fmt.Sprintf(`a{foo="%d"}`, i))
|
|
right[i] = ts(fmt.Sprintf(`b{foo="%d"}`, i))
|
|
}
|
|
bfa := &binaryOpFuncArg{
|
|
be: mustParseMetricsQL("a or b"),
|
|
left: left,
|
|
right: right,
|
|
}
|
|
benchmarkBinaryOpOr(b, bfa)
|
|
})
|
|
b.Run("tss:1000 or on() vector(0)", func(b *testing.B) {
|
|
left := make([]*timeseries, 1000)
|
|
for i := range left {
|
|
left[i] = ts(fmt.Sprintf(`a{foo="%d"}`, i))
|
|
}
|
|
bfa := &binaryOpFuncArg{
|
|
be: mustParseMetricsQL("a or on() vector(0)"),
|
|
left: left,
|
|
right: []*timeseries{ts(``)},
|
|
}
|
|
benchmarkBinaryOpOr(b, bfa)
|
|
})
|
|
}
|
|
|
|
func benchmarkBinaryOpOr(b *testing.B, bfa *binaryOpFuncArg) {
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_, err := binaryOpOr(bfa)
|
|
if err != nil {
|
|
b.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
}
|
|
})
|
|
}
|