mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2026-10-20 00:16:32 +00:00
perf: probe with MatchFirst in HasMatcher to avoid slice allocation
HasMatcher only needs to know whether a child subtree contains a match,
but it tested len(m.MatchAll(c)) > 0 after wrapping the matcher with
SingleMatcher. SingleMatcher.MatchAll computes MatchFirst internally and
then wraps the result in a throwaway []*html.Node{node}, allocating a
one-element slice per matching child subtree just to check a boolean.
When the matcher exposes MatchFirst, probe it directly and test the returned
node against nil, avoiding the allocation entirely. This is the same trick
already used in SingleMatcher.MatchAll, just applied at the HasMatcher call
site so the intermediate slice is never built in the first place. The
SingleMatcher path is kept as a fallback for matchers that don't implement
MatchFirst.
benchstat (count=10):
| before | after |
| sec/op | sec/op vs base|
Has-8 | 2.675µ ± 17% | 2.208µ ± 17% -17.47%|
| before | after |
| B/op | B/op vs base |
Has-8 | 360.0 ± 0% | 240.0 ± 0% -33.33% |
| before | after |
|allocs/op | allocs/op vs base |
Has-8 | 20.00 ± 0% | 6.000 ± 0% -70.00% |
This commit is contained in:
@@ -87,6 +87,24 @@ func (s *Selection) Has(selector string) *Selection {
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) HasMatcher(m Matcher) *Selection {
|
||||
result := make([]*html.Node, 0, len(s.Nodes))
|
||||
|
||||
// If the matcher can probe for the first match in a subtree without
|
||||
// building a result slice, use that: it avoids a throwaway one-element
|
||||
// slice allocation for every matching child subtree.
|
||||
if mf, ok := m.(interface {
|
||||
MatchFirst(*html.Node) *html.Node
|
||||
}); ok {
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type == html.ElementNode && mf.MatchFirst(c) != nil {
|
||||
result = append(result, n)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return pushStack(s, result)
|
||||
}
|
||||
|
||||
m = SingleMatcher(m)
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
|
||||
Reference in New Issue
Block a user