Files
metrics/app/vmselect/promql/binary_op_timing_test.go
Roman KhavronenkoandGitHub 4bd21702e7 app/vmselect/promql: optimize binary operator or for common cases (#8489)
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]>
2025-03-14 12:07:22 +01:00

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 &timeseries{
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)
}
}
})
}