perf: call Match/isInSlice directly in Until traversal helpers

getParentsNodes and getSiblingNodes created a newSingleSelection per
ancestor/sibling node just to call IsMatcher or IsNodes for the
until-stop condition. Since these are always single-node checks, call
stopm.Match(p) and isInSlice(stopNodes, p) directly, avoiding the
Selection allocation and the method dispatch overhead.

name                      old ns/op   new ns/op   delta
ParentsUntil-8            30700       16100       -47.6%
ParentsUntilNodes-8       82700       28400       -65.7%
NextUntil-8               38000       25700       -32.4%
NextUntilNodes-8          13700       5900        -56.9%
PrevUntil-8               109600      82600       -24.6%
PrevUntilNodes-8          11600       4600        -60.3%

name                      old B/op    new B/op    delta
ParentsUntil-8            11528       4248        -63.2%
ParentsUntilNodes-8       39040       10448       -73.2%
NextUntil-8               8592        7712        -10.2%
NextUntilNodes-8          5536        1304        -76.4%
PrevUntil-8               30344       28280       -6.8%
PrevUntilNodes-8          4248        920         -78.3%

name                      old allocs  new allocs  delta
ParentsUntil-8            304         44          -85.5%
ParentsUntilNodes-8       904         60          -93.4%
NextUntil-8               216         106         -50.9%
NextUntilNodes-8          156         27          -82.7%
PrevUntil-8               408         150         -63.2%
PrevUntilNodes-8          129         23          -82.2%
This commit is contained in:
jvoisin
2026-05-24 22:48:16 +02:00
parent 83a0a8bbd8
commit b7030c526f
+4 -9
View File
@@ -562,13 +562,12 @@ func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
for p := n.Parent; p != nil; p = p.Parent {
sel := newSingleSelection(p, nil)
if stopm != nil {
if sel.IsMatcher(stopm) {
if stopm.Match(p) {
break
}
} else if len(stopNodes) > 0 {
if sel.IsNodes(stopNodes...) {
if isInSlice(stopNodes, p) {
break
}
}
@@ -589,13 +588,9 @@ func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNo
if st == siblingNextUntil || st == siblingPrevUntil {
f = func(n *html.Node) bool {
if untilm != nil {
// Matcher-based condition
sel := newSingleSelection(n, nil)
return sel.IsMatcher(untilm)
return untilm.Match(n)
} else if len(untilNodes) > 0 {
// Nodes-based condition
sel := newSingleSelection(n, nil)
return sel.IsNodes(untilNodes...)
return isInSlice(untilNodes, n)
}
return false
}