From 8ea13ea0178c5119999ae5b8ea5724de8f79914e Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 27 May 2026 11:52:43 +0200 Subject: [PATCH] perf: drop grep/closure indirection in winnowNodes In the same spirit as #545, replaces costly grep() with a loop. While escape analysis managed to stack-allocate the per-node Selection through grep's inlining, the closure capture itself (nodes and keep) was still heap-allocated. Local benchmarks are showing a 30% allocs/op reduction, and a ~10-20% reduction in sec/op. --- filter.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/filter.go b/filter.go index 271f050..20a31de 100644 --- a/filter.go +++ b/filter.go @@ -144,18 +144,26 @@ func winnow(sel *Selection, m Matcher, keep bool) []*html.Node { // to get rid of (Not) the matching elements. func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node { if len(nodes)+len(sel.Nodes) < minNodesForSet { - return grep(sel, func(i int, s *Selection) bool { - return isInSlice(nodes, s.Get(0)) == keep - }) + var result []*html.Node + for _, n := range sel.Nodes { + if isInSlice(nodes, n) == keep { + result = append(result, n) + } + } + return result } set := make(map[*html.Node]bool) for _, n := range nodes { set[n] = true } - return grep(sel, func(i int, s *Selection) bool { - return set[s.Get(0)] == keep - }) + var result []*html.Node + for _, n := range sel.Nodes { + if set[n] == keep { + result = append(result, n) + } + } + return result } // Filter based on a function test, and the indicator to keep (Filter) or