From e4789cf89f81a5dfaaabf813f316af267f8630bc Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 11 May 2025 12:17:22 +0100 Subject: [PATCH] [PERF] PromQL: Reduce allocations when walking syntax tree Currently it allocates at least once on every recursion, and since `append` gives no extra space for 1 or 2 elements, will allocate several times for even modestly complex expressions. Instead, allocate space for 4 elements to begin, and defer adding another until we need it. Add a note that `path` may get overwritten; this was true previously. Signed-off-by: Bryan Boreham --- promql/parser/ast.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/promql/parser/ast.go b/promql/parser/ast.go index 882d3ccb78..2e54f3fe67 100644 --- a/promql/parser/ast.go +++ b/promql/parser/ast.go @@ -334,10 +334,13 @@ func Walk(v Visitor, node Node, path []Node) error { if v, err = v.Visit(node, path); v == nil || err != nil { return err } - path = append(path, node) + var pathToHere []Node // Initialized only when needed. for e := range ChildrenIter(node) { - if err := Walk(v, e, path); err != nil { + if pathToHere == nil { + pathToHere = append(path, node) + } + if err := Walk(v, e, pathToHere); err != nil { return err } } @@ -371,8 +374,10 @@ func (f inspector) Visit(node Node, path []Node) (Visitor, error) { // Inspect traverses an AST in depth-first order: It starts by calling // f(node, path); node must not be nil. If f returns a nil error, Inspect invokes f // for all the non-nil children of node, recursively. +// Note: path may be overwritten after f returns; copy path if you need to retain it. func Inspect(node Node, f inspector) { - Walk(f, node, nil) //nolint:errcheck + var pathBuf [4]Node // To reduce allocations during recursion. + Walk(f, node, pathBuf[:0]) //nolint:errcheck } // ChildrenIter returns an iterator over all child nodes of a syntax tree node.