58 Commits
Author SHA1 Message Date
jvoisin 36f7b3b68e cleanup: thread []*html.Node through winnow, drop temp Selection
winnow only ever reads sel.Nodes; the *Selection parameter was
incidental. Change its signature to winnow(nodes []*html.Node, ...)
and update its three callers (FilterMatcher, NotMatcher,
filterAndPush). filterAndPush no longer needs to fabricate a
throwaway Selection to satisfy the call.

While this change has o measurable runtime effect, it simplifies the code a bit,
by making winnow's signature clearer, and removes a now-useless line and its
associated comment in filterAndPush
2026-06-05 22:35:48 +02:00
Martin Angers 2955b8bf81 Merge pull request #568 from jvoisin/grnkgnr
perf: collect parent nodes inline in getParentNodes
2026-06-01 11:35:56 -04:00
jvoisin 116f677017 perf: collect parent nodes inline in getParentNodes
getParentNodes ran through mapNodes with a callback that allocated a
throwaway one-element []*html.Node{n.Parent} for every source node. Since
sibling nodes share the same parent, almost all of those slices were
immediately discarded as duplicates.

This commit collects the parents directly in a single loop, deduplicating via a
set as we go, so no per-node slice is allocated.

benchstat (count=10, all p=0.000):

                 |   before    |             after            |
                 |   sec/op    |    sec/op     vs base        |
Parent-8         | 38.54µ ± 21%|  17.49µ ± 14%  -54.63%       |
ParentFiltered-8 | 46.57µ ± 13%|  20.91µ ± 24%  -55.09%       |

                 |   before    |             after            |
                 |    B/op     |     B/op      vs base        |
Parent-8         | 13.24Ki ± 0%|  10.28Ki ± 0%  -22.36%       |
ParentFiltered-8 | 13.49Ki ± 0%|  10.58Ki ± 0%  -21.60%       |

                 |   before    |            after             |
                 |  allocs/op  | allocs/op   vs base          |
Parent-8         |  384.0 ± 0% | 10.00 ± 0%  -97.40%          |
ParentFiltered-8 |  392.0 ± 0% | 19.00 ± 0%  -95.15%          |
2026-05-30 22:52:34 +02:00
jvoisin f4b3a3e2c4 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.
2026-05-29 23:45:29 +02:00
Martin Angers bd4f51414c Merge branch 'master' into precise 2026-05-28 10:21:55 -04:00
Martin Angers c38303d8bf Merge pull request #550 from jvoisin/nomap
perf: skip dedup map in mapNodes for single-node selections
2026-05-28 10:05:55 -04:00
jvoisin 5f07eebb27 perf: presize node-set maps in mapNodes, ClosestNodes and winnowNodes
Three internal helpers build a map[*html.Node]bool from a known set of
nodes (or use one as a dedup accumulator over a known number of input
nodes) but allocate the map without a size hint. They then incur
grow-and-double rehashing as entries are inserted.

Pass len(nodes) as the size hint at make time:

- traversal.go mapNodes: dedup map shared by appendWithoutDuplicates
  across every per-node result. mapNodes is the engine behind most
  traversal helpers (Parents, Children, Next/Prev, Siblings, Find*,
  Contents, ...), so the impact is broad.
- traversal.go ClosestNodes: target-node set built from nodes...
- filter.go winnowNodes: large-N path's lookup set built from nodes...

Double-digit performance gains across ~all benchmarks.
2026-05-27 23:11:36 +02:00
jvoisin 25b585d336 perf: skip dedup map in mapNodes for single-node selections
mapNodes always allocated a map[*html.Node]bool for deduplication, even
when called with a single source node where duplicates cannot arise.
Short-circuit to return the callback result directly when len(nodes)==1.
Also add a special case for when there are zero nodes.

This benefits all traversal methods (Find, Children, Parent, Next, Prev,
etc.) when operating on a single-node selection, which is the common
case after First(), Eq(), or directly on a Document.

name      old ns/op   new ns/op   delta
Find-8    23200       14200       -38.8%

name      old B/op    new B/op    delta
Find-8    4632        1496        -67.7%

name      old allocs  new allocs  delta
Find-8    27          13          -51.9%
2026-05-27 21:17:38 +02:00
jvoisin b7030c526f 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%
2026-05-24 22:48:16 +02:00
Martin Angers 153f1ea15a Add notes on cascadia behavior that may differ from jQuery`s 2024-02-29 09:22:34 -05:00
Martin Angers 5dccaa115b optimize winnowNodes and ClosestNodes in the same way 2016-08-28 11:55:36 -04:00
Martin Angers d85ad7e2ed remove var declaration 2016-08-27 22:44:10 -04:00
Martin Angers 7bd630023a reuse the deduplication set map in mapNodes 2016-08-27 22:25:02 -04:00
Martin Angers 2ab590df05 use a failing matcher if selector fails to compile, adjust tests 2016-06-15 09:27:17 -04:00
HARUYAMA Seigo d9b2b5ed51 cascadia has moved.
code.google.com/p/cascadia
->
github.com/andybalholm/cascadia
2015-03-28 11:15:11 +09:00
Martin Angers fb2312a925 add final *Matcher* overloads 2014-11-07 12:19:01 -05:00
Martin Angers ef025592df more *Matcher overloads of selector string methods 2014-11-07 11:37:50 -05:00
Martin Angers 875b65f385 finish transition for Matcher-based overloads 2014-11-07 08:36:55 -05:00
Martin Angers 251095afb5 start adding matcher functions 2014-11-06 21:43:08 -05:00
Martin Angers cc9b6f8c6f Merge branch 'master' into manipulation 2014-11-06 20:46:11 -05:00
Martin Angers 040cf98281 change paths to golang.org/x/, fails to build because cascadia has to switch too 2014-11-05 20:30:33 -05:00
Andrew Stone 9848519841 filter: Add *Selector for filtering methods that accepts a pre-compiled cascadia selector. 2014-10-24 11:49:05 -04:00
Martin Angers cf82003f7e add test to detect a   from a call to Text() 2014-04-25 15:28:38 -04:00
Martin Angers 6938944908 final linting 2014-04-11 12:06:28 -04:00
Martin Angers 28cf4a77e1 more linting 2014-04-11 12:04:03 -04:00
Martin Angers 2c6687f6d3 lint 2014-04-11 12:00:17 -04:00
Dobrosław Żybort 474b169363 Update exp/html import paths 2013-02-12 20:32:16 +01:00
Martin Angers 971be3c4e4 add nodes and selection overloads for Closest 2012-11-08 21:08:24 -05:00
Martin Angers 2f94000c0a start closest with selector string 2012-11-04 20:31:03 -05:00
Martin Angers 0f850e5c82 remove commented code 2012-10-18 21:18:56 -04:00
Martin Angers 2371acd273 refactor Next...() and Prev...() functions to use linked list, much faster 2012-09-17 11:01:00 -04:00
Martin Angers d5e9341ca1 add PrevFilteredUntil...() with tests 2012-09-10 10:04:00 -04:00
Martin Angers 2fc9009621 add NextFilteredUntil...() with tests 2012-09-10 09:09:32 -04:00
Martin Angers ab7dea3da3 update to use new exp/html package 2012-09-09 18:44:47 -04:00
Martin Angers 5da274a53d add NextUntil...() with tests 2012-09-06 14:40:15 -04:00
Martin Angers eea8ab69ed add PrevUntilSelection() and PrevUntilNodes() with tests 2012-09-06 10:57:54 -04:00
Martin Angers 7dcaa4213b add PrevUntil() with tests 2012-09-06 10:39:30 -04:00
Martin Angers a9c4ad4c8f start work on PrevUntil() and NextUntil() 2012-09-06 10:27:42 -04:00
Martin Angers bf960f0855 fix order for prevAll 2012-09-06 09:20:24 -04:00
Martin Angers 2d885ccd3a add tests for End() rollbacks, doc 2012-09-05 09:35:53 -04:00
Martin Angers 3aaca8f8ea refactor of Find() methods to use mapNodes 2012-09-04 15:18:51 -04:00
Martin Angers 6b27ec19a5 refactor Children() and Contents() to use mapNodes internally 2012-09-04 14:56:20 -04:00
Martin Angers af4a62048b add PrevAll() and PrevAllFiltered() with tests 2012-09-04 13:44:20 -04:00
Martin Angers 19c7e27fc0 add NextAll(), NextAllFiltered(), refactor all filtered methods 2012-09-04 13:34:52 -04:00
Martin Angers 9394a923e0 add Prev() and PrevFiltered() with tests 2012-09-04 13:06:03 -04:00
Martin Angers ed3b9aca8a add Next() and NextFiltered() with tests 2012-09-04 12:07:59 -04:00
Martin Angers ff0cbc32c4 add SiblingsFiltered() and tests 2012-09-04 11:20:20 -04:00
Martin Angers b26e37d262 add Siblings() with tests 2012-09-04 11:15:09 -04:00
Martin Angers f461c82030 add ParentsFilteredUntil...() with tests 2012-09-04 10:58:44 -04:00
Martin Angers d03482bacd add ParentsUntil...() with tests 2012-09-04 10:39:59 -04:00