Repository navigation
Conversation
|
@tstreule Can you verify that your change aligns with Servo AI contribution policy: https://book.servo.org/policy/ai-usage.html? In short, LLMs are fine for preparation / investigation / review, but no generated code, PR descriptions or comments are allowed. |
|
No AI was used for most of the work, like code and testing. However, I used AI to rename variables and clean up the PR description. If you like, I can gladly rewrite the PR description. Sorry for any inconvenience! |
Thanks! Yes, if you don't mind please rewrite the description in your own words. |
d963a6a to
6566229
Compare
@mrobinson Sorry that it took a bit longer. Should be ready for your review now. |
|
Found this same issue, adding timing information for reference: On html5ever 0.39.0, a single ~1 MiB end tag carrying ~150,000 distinct attribute names did not finish within 25 s. End tags go through the same With a per-tag |
6566229 to
36515e5
Compare
The tokenizer compared each attribute name with every earlier one on the tag, so a tag with n attributes cost O(n^2): 80,000 attributes took about 1.3 s to tokenize. From ten attributes on, look the name up in a set of the tag's names instead, as Blink does; below that, the scan is cheaper than hashing. Which attribute wins, the parse error and `had_duplicate_attributes` stay the same. Fixes servo#796. Part of servo#289.
36515e5 to
395b8ae
Compare
Fixes #796. Part of #289.
Problem
Until now
Tokenizer::finish_attributescanned every attribute already on the tag for each new name. So one tag with n attributes costs O(n²).Solution
Following what's proposed in #796, we adjust the tokenizer to mirror how Blink does it (
kMinimumNumAttributesToDedupWithHashinatomic_html_token.h):HashSet<LocalName>), one step regardless of how many attributes there are.Changed.
The tokenizer in
html5ever/src/tokenizer/mod.rsgets a new fieldcurrent_tag_attr_names: RefCell<HashSet<LocalName>>and methodis_duplicate_attribute, which returns whether the tag already has an attribute with that name.Behavior is unchanged: the first occurrence wins, later ones are dropped with the
Duplicate attributeparse error, andhad_duplicate_attributesis set.Tests
Tags with fewer than 10 attributes still take the linear scan, which the html5lib tokenizer tests already cover. Four new unit tests in
tokenizer::testcover the set:duplicate_of_an_attribute_before_the_threshold: a duplicate ofa0on tags with 9, 10 and 11 attributes. From 10 on,a0is found among the names put into the set when it is first filled.duplicate_of_an_attribute_after_the_threshold:id=first id=secondafter 12 attributes.idis found among the names added to the set one by one.no_duplicates_on_a_next_tag_with_the_same_attributes: two tags with the same 12 attributes report no duplicates, so the set is cleared between tags.duplicate_on_a_next_tag_with_other_attributes: a duplicate on a second tag with other names is still found, so the set is filled again for each tag.They check the kept attributes, the
Duplicate attributeerrors andhad_duplicate_attributes. The threshold is a module-level constant,MIN_ATTRS_TO_DEDUP_WITH_HASH, so the tests follow it if it changes.Timing
The program.
Results.
Release build, Apple M1 Pro, 5 runs per side. Median, with the range in brackets:
7760920)36515e5)While
maingrows about 4x per doubling on the attribute count (quadratic), this branch grows about 2x per doubling (linear).