mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2026-10-19 16:15:33 +00:00
perf: avoid per-node Selection allocation in winnow Not path
The winnow() "Not" path routed through grep(), which called newSingleSelection() for every node, allocating a Selection and a 1-element []*html.Node slice on the heap per node, only to immediately unwrap it via s.Get(0) for m.Match(). This commit call m.Match(n) directly and pre-allocate the result slice. It also adds BenchmarkNotMatcher to isolate the winnow path from compileMatcher overhead. Here are my local benchmark results, for BenchmarkNotMatcher with 373 <li> nodes, and a pre-compiled matcher: old ns/op new ns/op delta 22750 19350 ~-15% old B/op new B/op delta 9384 3120 -66.7% old allocs new allocs delta 11 2 -81.8%
This commit is contained in:
@@ -2,6 +2,8 @@ package goquery
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/cascadia"
|
||||
)
|
||||
|
||||
func BenchmarkFilter(b *testing.B) {
|
||||
@@ -40,6 +42,25 @@ func BenchmarkNot(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotMatcher(b *testing.B) {
|
||||
var n int
|
||||
|
||||
b.StopTimer()
|
||||
sel := DocW().Find("li")
|
||||
m := cascadia.MustCompile(".toclevel-2")
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if n == 0 {
|
||||
n = sel.NotMatcher(m).Length()
|
||||
} else {
|
||||
sel.NotMatcher(m)
|
||||
}
|
||||
}
|
||||
if n != 371 {
|
||||
b.Fatalf("want 371, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterFunction(b *testing.B) {
|
||||
var n int
|
||||
|
||||
|
||||
@@ -130,10 +130,14 @@ func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
|
||||
if keep {
|
||||
return m.Filter(sel.Nodes)
|
||||
}
|
||||
// Use grep
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return !m.Match(s.Get(0))
|
||||
})
|
||||
// Not path: call Match directly on each node, no Selection wrapper needed
|
||||
result := make([]*html.Node, 0, len(sel.Nodes))
|
||||
for _, n := range sel.Nodes {
|
||||
if !m.Match(n) {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Filter based on an array of nodes, and the indicator to keep (Filter) or
|
||||
|
||||
Reference in New Issue
Block a user