[PERF] PromQL: reuse matching data structures in VectorBinop/And/Or/Unless

Signed-off-by: Linas Medziunas <linas.medziunas@gmail.com>
This commit is contained in:
Linas Medziunas 2025-12-09 09:44:07 +02:00
parent 8721871cf7
commit cde7bbf9e7

View file

@ -1208,10 +1208,12 @@ type EvalNodeHelper struct {
lblResultBuf []byte
// For binary vector matching.
rightSigs map[int]Sample
matchedSigs map[int]map[uint64]struct{}
resultMetric map[string]labels.Labels
numSigs int
rightSigs []Sample
sigsPresent []bool
matchedSigs []map[uint64]struct{}
matchedSigsPresent []bool
resultMetric map[string]labels.Labels
numSigs int
// For info series matching.
rightStrSigs map[string]Sample
@ -1220,6 +1222,44 @@ type EvalNodeHelper struct {
enableDelayedNameRemoval bool
}
func (enh *EvalNodeHelper) resetSigsPresent() []bool {
if len(enh.sigsPresent) == 0 {
enh.sigsPresent = make([]bool, enh.numSigs)
} else {
clear(enh.sigsPresent)
}
return enh.sigsPresent
}
func (enh *EvalNodeHelper) resetMatchedSigsPresent() []bool {
if len(enh.matchedSigsPresent) == 0 {
enh.matchedSigsPresent = make([]bool, enh.numSigs)
} else {
clear(enh.matchedSigsPresent)
}
return enh.matchedSigsPresent
}
func (enh *EvalNodeHelper) resetRightSigs() []Sample {
if enh.rightSigs == nil {
enh.rightSigs = make([]Sample, enh.numSigs)
} else {
clear(enh.rightSigs)
}
return enh.rightSigs
}
func (enh *EvalNodeHelper) resetMatchedSigs() []map[uint64]struct{} {
if enh.matchedSigs == nil {
enh.matchedSigs = make([]map[uint64]struct{}, enh.numSigs)
} else {
for i := range enh.matchedSigs {
clear(enh.matchedSigs[i])
}
}
return enh.matchedSigs
}
func (enh *EvalNodeHelper) resetBuilder(lbls labels.Labels) {
if enh.lb == nil {
enh.lb = labels.NewBuilder(lbls)
@ -2791,7 +2831,7 @@ func (*evaluator) VectorAnd(lhs, rhs Vector, matching *parser.VectorMatching, lh
}
// Ordinals of signatures present on the right-hand side.
rightSigOrdinalsPresent := make([]bool, enh.numSigs)
rightSigOrdinalsPresent := enh.resetSigsPresent()
for _, sh := range rhsh {
rightSigOrdinalsPresent[sh.sigOrdinal] = true
}
@ -2817,7 +2857,7 @@ func (*evaluator) VectorOr(lhs, rhs Vector, matching *parser.VectorMatching, lhs
return enh.Out
}
leftSigOrdinalsPresent := make([]bool, enh.numSigs)
leftSigOrdinalsPresent := enh.resetSigsPresent()
// Add everything from the left-hand-side Vector.
for i, ls := range lhs {
leftSigOrdinalsPresent[lhsh[i].sigOrdinal] = true
@ -2844,7 +2884,7 @@ func (*evaluator) VectorUnless(lhs, rhs Vector, matching *parser.VectorMatching,
}
// Ordinals of signatures present on the right-hand side.
rightSigOrdinalsPresent := make([]bool, enh.numSigs)
rightSigOrdinalsPresent := enh.resetSigsPresent()
for _, sh := range rhsh {
rightSigOrdinalsPresent[sh.sigOrdinal] = true
}
@ -2876,19 +2916,16 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
}
// All samples from the rhs by their join signature ordinal.
if enh.rightSigs == nil {
enh.rightSigs = make(map[int]Sample, len(enh.Out))
} else {
clear(enh.rightSigs)
}
rightSigs := enh.rightSigs
rightSigs := enh.resetRightSigs()
rightSigsPresent := enh.resetSigsPresent()
// Add all rhs samples to a map so we can easily find matches later.
for i, rs := range rhs {
sigOrd := rhsh[i].sigOrdinal
// The rhs is guaranteed to be the 'one' side. Having multiple samples
// with the same signature means that the matching is many-to-many.
if duplSample, found := rightSigs[sigOrd]; found {
if rightSigsPresent[sigOrd] {
duplSample := rightSigs[sigOrd]
// oneSide represents which side of the vector represents the 'one' in the many-to-one relationship.
oneSide := "right"
if matching.Card == parser.CardOneToMany {
@ -2900,16 +2937,22 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
";many-to-many matching not allowed: matching labels must be unique on one side", matchedLabels.String(), oneSide, rs.Metric.String(), duplSample.Metric.String())
}
rightSigs[sigOrd] = rs
rightSigsPresent[sigOrd] = true
}
// Tracks the matching by signature ordinals. For one-to-one operations the value is nil.
// For many-to-one the value is a set of hashes to detect duplicated result elements.
if enh.matchedSigs == nil {
enh.matchedSigs = make(map[int]map[uint64]struct{}, len(rightSigs))
var (
// Tracks the match-signature for one-to-one operations.
matchedSigsPresent []bool
// Tracks the match-signature for many-to-one operations, the value is a set of signatures
// to detect duplicated result elements.
matchedSigs []map[uint64]struct{}
)
if matching.Card == parser.CardOneToOne {
matchedSigsPresent = enh.resetMatchedSigsPresent()
} else {
clear(enh.matchedSigs)
matchedSigs = enh.resetMatchedSigs()
}
matchedSigs := enh.matchedSigs
var lastErr error
@ -2938,26 +2981,26 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
}
}
metric := resultMetric(ls.Metric, rs.Metric, op, matching, enh)
if !ev.enableDelayedNameRemoval && returnBool {
metric = metric.DropReserved(schema.IsMetadataLabel)
}
insertedSigs, exists := matchedSigs[sigOrd]
dropMetricName := !ev.enableDelayedNameRemoval && returnBool
metric := resultMetric(ls.Metric, rs.Metric, op, matching, dropMetricName, enh)
if matching.Card == parser.CardOneToOne {
if exists {
if matchedSigsPresent[sigOrd] {
ev.errorf("multiple matches for labels: many-to-one matching must be explicit (group_left/group_right)")
}
matchedSigs[sigOrd] = nil // Set existence to true.
matchedSigsPresent[sigOrd] = true
} else {
// In many-to-one matching the grouping labels have to ensure a unique metric
// for the result Vector. Check whether those labels have already been added for
// the same matching labels.
insertSig := metric.Hash()
if !exists {
insertedSigs = map[uint64]struct{}{}
matchedSigs[sigOrd] = insertedSigs
} else if _, duplicate := insertedSigs[insertSig]; duplicate {
if matchedSigs[sigOrd] == nil {
matchedSigs[sigOrd] = map[uint64]struct{}{}
}
insertedSigs := matchedSigs[sigOrd]
if _, duplicate := insertedSigs[insertSig]; duplicate {
ev.errorf("multiple matches for labels: grouping labels must ensure unique matches")
}
insertedSigs[insertSig] = struct{}{}
@ -2980,8 +3023,12 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
for i, ls := range lhs {
sigOrd := lhsh[i].sigOrdinal
rs, found := rightSigs[sigOrd] // Look for a match in the rhs Vector.
if !found {
var rs Sample
if rightSigsPresent[sigOrd] {
// Found a match in the rhs.
rs = rightSigs[sigOrd]
} else {
// Have to fall back to the fill value.
fill := matching.FillValues.RHS
if fill == nil {
continue
@ -2998,8 +3045,11 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
// For any rhs samples which have not been matched, check if we need to
// perform the operation with a fill value from the lhs.
if fill := matching.FillValues.LHS; fill != nil {
for sigOrd, rs := range rightSigs {
if _, matched := matchedSigs[sigOrd]; matched {
for i, rs := range rhs {
sigOrd := rhsh[i].sigOrdinal
if (len(matchedSigsPresent) > 0 && matchedSigsPresent[sigOrd]) ||
(len(matchedSigs) > 0 && matchedSigs[sigOrd] != nil) {
continue // Already matched.
}
ls := Sample{
@ -3016,7 +3066,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
// resultMetric returns the metric for the given sample(s) based on the Vector
// binary operation and the matching options.
func resultMetric(lhs, rhs labels.Labels, op parser.ItemType, matching *parser.VectorMatching, enh *EvalNodeHelper) labels.Labels {
func resultMetric(lhs, rhs labels.Labels, op parser.ItemType, matching *parser.VectorMatching, dropMetricName bool, enh *EvalNodeHelper) labels.Labels {
if enh.resultMetric == nil {
enh.resultMetric = make(map[string]labels.Labels, len(enh.Out))
}
@ -3034,7 +3084,7 @@ func resultMetric(lhs, rhs labels.Labels, op parser.ItemType, matching *parser.V
str := string(enh.lblResultBuf)
enh.resetBuilder(lhs)
if changesMetricSchema(op) {
if dropMetricName || changesMetricSchema(op) {
// Setting empty Metadata causes the deletion of those if they exists.
schema.Metadata{}.SetToLabels(enh.lb)
}