From bfc9c5940d4d0cc991b419c22bafed064ca19cc9 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 27 May 2026 12:11:29 +0200 Subject: [PATCH] perf: walk s.Nodes directly in HasNodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HasNodes routed through FilterFunction -> winnowFunction -> grep, which allocated a single-node Selection and a closure capture per source element, just to call sel.Contains(n) which is itself a one-iteration sliceContains over the wrapped node. This commit inlines a direct double loop over s.Nodes x nodes calling nodeContains, and push the result via pushStack. name old allocs/op new allocs/op delta Has-8 55.00 ± 0% 24.00 ± 0% -56.36% (p=0.000) HasNodes-8 752.00 ± 0% 4.00 ± 0% -99.47% (p=0.000) HasSelection-8 752.00 ± 0% 3.00 ± 0% -99.60% (p=0.000) --- filter.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/filter.go b/filter.go index 271f050..e586923 100644 --- a/filter.go +++ b/filter.go @@ -93,15 +93,16 @@ func (s *Selection) HasMatcher(m Matcher) *Selection { // descendant that matches one of the nodes. // It returns a new Selection object with the matching elements. func (s *Selection) HasNodes(nodes ...*html.Node) *Selection { - return s.FilterFunction(func(_ int, sel *Selection) bool { - // Add all nodes that contain one of the specified nodes - for _, n := range nodes { - if sel.Contains(n) { - return true + var result []*html.Node + for _, n := range s.Nodes { + for _, candidate := range nodes { + if nodeContains(n, candidate) { + result = append(result, n) + break } } - return false - }) + } + return pushStack(s, result) } // HasSelection reduces the set of matched elements to those that have a