mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2026-10-19 16:15:33 +00:00
perf: presize result slice in getChildrenWithSiblingType
For the sibling types that collect every match (siblingAll,
siblingAllIncludingNonElements, siblingPrevAll, siblingNextAll), do a
cheap first-pass pointer walk to count the matches, then make the result
slice with that exact capacity. This removes the repeated append-driven
slice growth that dominated allocations on these hot traversal paths.
The Until cases are left untouched because counting would require running
the user predicate twice, and the single-result Next/Prev cases have
nothing to presize.
This drives Children/Contents/Siblings/Next*/Prev* through fewer
allocations:
allocs/op vs base B/op vs base
Siblings -57.23% -9.23%
SiblingsFiltered -54.49% -8.94%
NextAll -59.82% -10.04%
NextAllFiltered -55.83% -9.65%
PrevAll -55.32% -10.66%
PrevAllFiltered -52.53% -10.40%
ChildrenFiltered -15.38% -6.45%
Contents -7.69% -1.73%
geomean -25.07% -3.88%
The Until and single Next/Prev benchmarks are unchanged (bit-identical
allocs/op), confirming the count pre-pass only touches the collect-all
paths.
The quite verbose comment on top of the change is there so that future
generations won't waste time wondering why this weird loop is here.
This commit is contained in:
@@ -657,6 +657,20 @@ func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *htm
|
||||
}
|
||||
}
|
||||
|
||||
// For the cases that collect every matching sibling, count them in a
|
||||
// cheap pointer walk first so the result slice can be sized exactly,
|
||||
// avoiding repeated slice growth. The Until cases are skipped (counting
|
||||
// would require running the predicate twice) and so are the single-result
|
||||
// Next/Prev cases.
|
||||
switch st {
|
||||
case siblingAll, siblingAllIncludingNonElements, siblingPrevAll, siblingNextAll:
|
||||
n := 0
|
||||
for c := iter(nil); c != nil; c = iter(c) {
|
||||
n++
|
||||
}
|
||||
result = make([]*html.Node, 0, n)
|
||||
}
|
||||
|
||||
for c := iter(nil); c != nil; c = iter(c) {
|
||||
// If this is an ...Until case, test before append (returns true
|
||||
// if the until condition is reached)
|
||||
|
||||
Reference in New Issue
Block a user