From b8d2d505f523adbd58f7273785a1b60ae76d94a7 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 12 May 2025 15:17:19 +0100 Subject: [PATCH] [PERF] PromQL: Replace some Sprintf with bytes.Buffer Goes faster due to reduced memory allocation. Signed-off-by: Bryan Boreham --- promql/parser/prettier.go | 2 +- promql/parser/printer.go | 144 +++++++++++++++++++++++++------------- 2 files changed, 96 insertions(+), 50 deletions(-) diff --git a/promql/parser/prettier.go b/promql/parser/prettier.go index 54726be4fe..90fb7a0cf9 100644 --- a/promql/parser/prettier.go +++ b/promql/parser/prettier.go @@ -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() { diff --git a/promql/parser/printer.go b/promql/parser/printer.go index c6e378a66d..00204abaef 100644 --- a/promql/parser/printer.go +++ b/promql/parser/printer.go @@ -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() }