From e9c347f211acb42e857e299510d30a8013f91cf0 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 1 Jun 2026 01:07:24 +0200 Subject: [PATCH] perf: scan adjacent element directly in Next/Prev sibling traversal Next() and Prev() request a single sibling per source node, and distinct source nodes always have distinct immediate siblings, so the results can never contain duplicates. Routing them through mapNodes (which allocates a dedup map and a throwaway one-element slice per node) and the general getChildrenWithSiblingType iterator is suboptimal. This commit adds a dedicated fast path in getSiblingNodes that scans straight to the adjacent element node and appends into a presized slice, skipping the dedup map and the iterator closure entirely. benchstat (count=10, all p=0.000): | old | new | | allocs | allocs vs base | Next-8 | 60.00 | 2.000 -96.67% | Prev-8 | 60.00 | 2.000 -96.67% | NextFiltered-8| 65.00 | 7.000 -89.23% | PrevFiltered-8| 65.00 | 7.000 -89.23% | sec/op: Next -89%, Prev -92%, Filtered variants -72% B/op: ~-80% --- traversal.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/traversal.go b/traversal.go index c495eea..91aa5c0 100644 --- a/traversal.go +++ b/traversal.go @@ -581,6 +581,28 @@ func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) // Internal implementation of sibling nodes that return a raw slice of matches. func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node { + // Next and Prev request a single sibling per node, and distinct source + // nodes have distinct immediate siblings, so there is nothing to dedup: + // scan directly to the adjacent element instead of going through mapNodes + // and the general getChildrenWithSiblingType iterator. + if st == siblingNext || st == siblingPrev { + adjacent := func(n *html.Node) *html.Node { return n.NextSibling } + if st == siblingPrev { + adjacent = func(n *html.Node) *html.Node { return n.PrevSibling } + } + + result := make([]*html.Node, 0, len(nodes)) + for _, n := range nodes { + for c := adjacent(n); c != nil; c = adjacent(c) { + if c.Type == html.ElementNode { + result = append(result, c) + break + } + } + } + return result + } + var f func(*html.Node) bool // If the requested siblings are ...Until, create the test function to