Repository navigation
Keep the xpath attribute selector's results per scope and current with the DOM - #56
Open
michaelthyregod wants to merge 2 commits into
Open
michaelthyregod wants to merge 2 commits into
michaelthyregod wants to merge 2 commits into
Conversation
…h the DOM AngleSharp caches parsed selectors, so one XPathAttrSelector serves every query and thread of a context. It kept a single scope and result list, so: - a query after a DOM change returned the old result - parallel queries on different documents overwrote each other's result - every element was looked up in a list, quadratic in the number of matches - Text printed a stray '$': *[xpath>'$//li'] The result is now kept per scope (ConditionalWeakTable), tagged with the document's MutationVersion and replaced as a whole when the document changed. Elements are looked up in a HashSet that compares references, because Node.Equals(INode) compares structure.
…ope only when needed The per-scope table cost a lookup for every element and a table for every new selector, which made a few cases slower than before. The last result is now checked first, the table only exists once a second scope shows up, and results of up to 8 nodes are compared by reference without hashing.
Author
|
Benchmarked with BenchmarkDotNet: master (4f0ff27) vs this PR, both on AngleSharp 1.8.6, net8.0, 3 rounds with the builds alternating, median per case. "Warm" repeats a query (core reuses its cached selector); "Cold" uses a fresh selector, as a first query or the first one after a DOM change. The first version made two cases slower (warm sparse-10000 2×, cold list-10 2.1×), so 3b1eb11 checks the last result first, creates the per-scope table only once a second scope shows up, and compares results of up to 8 nodes by reference.
Cold queries allocate a set for the result: +424 B at 10 matches, about +200 KB at 10,000. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AngleSharp caches parsed selectors, so one
XPathAttrSelectorserves every query and thread of a browsing context. It kept a single scope and result list, so:li, and*[xpath>'//li']still finds 3);List, which is quadratic in the number of matches;Textprinted a stray$:*[xpath>'$//li'].The last result answers the elements of one query; a per-scope table (
ConditionalWeakTable) is only created once a second scope shows up. Each result is tagged with the document'sMutationVersion(public since AngleSharp 1.8.2) and replaced as a whole when the document has changed. Results of up to 8 nodes are compared by reference, bigger ones through aHashSetthat compares references, becauseNode.Equals(INode)compares structure.Like the counter itself, the result doesn't follow changes made inside a detached subtree or by the parser while the document is still loading.
Six new tests (one with 20 matches for the
HashSetpath). Benchmark results are in the comment below.🤖 Generated with Claude Code