[PERF] PromQL: Replace some Sprintf with bytes.Buffer

Goes faster due to reduced memory allocation.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
This commit is contained in:
Bryan Boreham 2025-05-12 15:17:19 +01:00
parent 49d9261693
commit b8d2d505f5
2 changed files with 96 additions and 50 deletions

View File

@ -54,7 +54,7 @@ func (e *AggregateExpr) Pretty(level int) string {
return s
}
s += e.getAggOpStr()
s += e.ShortString()
s += "(\n"
if e.Op.IsAggregatorWithParam() {

View File

@ -53,49 +53,57 @@ func (node *EvalStmt) String() string {
}
func (es Expressions) String() (s string) {
if len(es) == 0 {
switch len(es) {
case 0:
return ""
case 1:
return es[0].String()
}
for _, e := range es {
s += e.String()
s += ", "
b := bytes.NewBuffer(make([]byte, 0, 1024))
b.WriteString(es[0].String())
for _, e := range es[1:] {
b.WriteString(", ")
b.WriteString(e.String())
}
return s[:len(s)-2]
return b.String()
}
func (node *AggregateExpr) String() string {
aggrString := node.getAggOpStr()
aggrString += "("
b := bytes.NewBuffer(make([]byte, 0, 1024))
node.writeAggOpStr(b)
b.WriteString("(")
if node.Op.IsAggregatorWithParam() {
aggrString += fmt.Sprintf("%s, ", node.Param)
b.WriteString(node.Param.String())
b.WriteString(", ")
}
aggrString += fmt.Sprintf("%s)", node.Expr)
b.WriteString(node.Expr.String())
b.WriteString(")")
return aggrString
return b.String()
}
func (node *AggregateExpr) ShortString() string {
aggrString := node.getAggOpStr()
return aggrString
b := bytes.NewBuffer(make([]byte, 0, 1024))
node.writeAggOpStr(b)
return b.String()
}
func (node *AggregateExpr) getAggOpStr() string {
aggrString := node.Op.String()
func (node *AggregateExpr) writeAggOpStr(b *bytes.Buffer) {
b.WriteString(node.Op.String())
switch {
case node.Without:
aggrString += fmt.Sprintf(" without (%s) ", joinLabels(node.Grouping))
b.WriteString(" without (")
writeLabels(b, node.Grouping)
b.WriteString(") ")
case len(node.Grouping) > 0:
aggrString += fmt.Sprintf(" by (%s) ", joinLabels(node.Grouping))
b.WriteString(" by (")
writeLabels(b, node.Grouping)
b.WriteString(") ")
}
return aggrString
}
func joinLabels(ss []string) string {
var bytea [1024]byte // On stack to avoid memory allocation while building the output.
b := bytes.NewBuffer(bytea[:0])
func writeLabels(b *bytes.Buffer, ss []string) {
for i, s := range ss {
if i > 0 {
b.WriteString(", ")
@ -106,7 +114,18 @@ func joinLabels(ss []string) string {
b.WriteString(s)
}
}
return b.String()
}
// writeStringsJoin is like strings.Join but appending to a bytes.Buffer.
func writeStringsJoin(b *bytes.Buffer, elems []string, sep string) {
if len(elems) == 0 {
return
}
b.WriteString(elems[0])
for _, s := range elems[1:] {
b.WriteString(sep)
b.WriteString(s)
}
}
func (node *BinaryExpr) returnBool() string {
@ -147,32 +166,54 @@ func (node *BinaryExpr) getMatchingStr() string {
}
func (node *DurationExpr) String() string {
var expr string
b := bytes.NewBuffer(make([]byte, 0, 1024))
node.writeTo(b)
return b.String()
}
func (node *DurationExpr) writeTo(b *bytes.Buffer) {
if node.Wrapped {
b.WriteByte('(')
}
switch {
case node.Op == STEP:
expr = "step()"
b.WriteString("step()")
case node.Op == MIN:
expr = fmt.Sprintf("min(%s, %s)", node.LHS, node.RHS)
b.WriteString("min(")
b.WriteString(node.LHS.String())
b.WriteString(", ")
b.WriteString(node.RHS.String())
b.WriteByte(')')
case node.Op == MAX:
expr = fmt.Sprintf("max(%s, %s)", node.LHS, node.RHS)
b.WriteString("max(")
b.WriteString(node.LHS.String())
b.WriteString(", ")
b.WriteString(node.RHS.String())
b.WriteByte(')')
case node.LHS == nil:
// This is a unary duration expression.
switch node.Op {
case SUB:
expr = fmt.Sprintf("%s%s", node.Op, node.RHS)
b.WriteString(node.Op.String())
b.WriteString(node.RHS.String())
case ADD:
expr = node.RHS.String()
b.WriteString(node.RHS.String())
default:
// This should never happen.
panic(fmt.Sprintf("unexpected unary duration expression: %s", node.Op))
}
default:
expr = fmt.Sprintf("%s %s %s", node.LHS, node.Op, node.RHS)
b.WriteString(node.LHS.String())
b.WriteByte(' ')
b.WriteString(node.Op.String())
b.WriteByte(' ')
b.WriteString(node.RHS.String())
}
if node.Wrapped {
return fmt.Sprintf("(%s)", expr)
b.WriteByte(')')
}
return expr
}
func (node *DurationExpr) ShortString() string {
@ -321,28 +362,33 @@ func (node *VectorSelector) String() string {
}
labelStrings = append(labelStrings, matcher.String())
}
offset := ""
switch {
case node.OriginalOffsetExpr != nil:
offset = fmt.Sprintf(" offset %s", node.OriginalOffsetExpr)
case node.OriginalOffset > time.Duration(0):
offset = fmt.Sprintf(" offset %s", model.Duration(node.OriginalOffset))
case node.OriginalOffset < time.Duration(0):
offset = fmt.Sprintf(" offset -%s", model.Duration(-node.OriginalOffset))
b := bytes.NewBuffer(make([]byte, 0, 1024))
b.WriteString(node.Name)
if len(labelStrings) != 0 {
b.WriteByte('{')
sort.Strings(labelStrings)
writeStringsJoin(b, labelStrings, ",")
b.WriteByte('}')
}
at := ""
switch {
case node.Timestamp != nil:
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
b.WriteString(" @ ")
fmt.Fprintf(b, "%.3f", float64(*node.Timestamp)/1000.0)
case node.StartOrEnd == START:
at = " @ start()"
b.WriteString(" @ start()")
case node.StartOrEnd == END:
at = " @ end()"
b.WriteString(" @ end()")
}
if len(labelStrings) == 0 {
return fmt.Sprintf("%s%s%s", node.Name, at, offset)
switch {
case node.OriginalOffsetExpr != nil:
b.WriteString(" offset ")
node.OriginalOffsetExpr.writeTo(b)
case node.OriginalOffset > time.Duration(0):
b.WriteString(" offset ")
b.WriteString(model.Duration(node.OriginalOffset).String())
case node.OriginalOffset < time.Duration(0):
b.WriteString(" offset -")
b.WriteString(model.Duration(-node.OriginalOffset).String())
}
sort.Strings(labelStrings)
return fmt.Sprintf("%s{%s}%s%s", node.Name, strings.Join(labelStrings, ","), at, offset)
return b.String()
}