Keep a persistent per-document rule cache, and make styles using var() or adjusted by the StyleAdjuster cacheable
Categories
(Core :: CSS Parsing and Computation, enhancement)
Tracking
()
People
(Reporter: mayankleoboy1, Unassigned)
References
(Blocks 1 open bug)
Details
Attachments
(10 external files)
| Reporter | ||
Comment 1•12 days ago
|
||
Keep the stylist's rule cache read-only during traversal, then merge the
thread-local caches into it so later traversals can reuse reset structs.
Track root-value generations and viewport sizes, invalidate on stylist and
device changes, and exclude element-owned rule paths from persistence.
Stop admitting new keys when the cache is full instead of clearing it.
Add cross-traversal regression coverage, including the Gecko-only
resolution preference change that requires a mochitest.
| Reporter | ||
Comment 2•12 days ago
|
||
Record the custom properties referenced by reset declarations, including
fallbacks. Match shared maps by identity, otherwise compare the referenced
values with their URL data and attr() taint. Keep attr() references and
properties invalidated by dependency cycles uncacheable, and preserve the
display/content inherited-style dependency flag when reusing reset structs.
Add WPT coverage for reset properties under different custom property maps.
| Reporter | ||
Comment 3•12 days ago
|
||
Save each reset struct before its first StyleAdjuster mutation. Cache those
pre-adjustment structs and restore them on a hit, then rerun the adjuster
for the current element. This allows adjusted styles to populate the cache
without reusing another element's adjustments.
| Reporter | ||
Comment 4•12 days ago
•
|
||
Emilio, what do you think of this initial idea?
Patches are large, but maybe the perf improvements are worthwhile. The cache could cache more things in the future i suppose. Some tuning of parameters could also be done.
I have not done any memory measurement.
Comment 5•11 days ago
|
||
The two patches are fairly independent aren't they? They should be measured separately.
The second one seems probably worth it given how wide spread var() is. The first one I'm a lot less sure about.
Comment 6•11 days ago
|
||
Also part 3 seems unlikely to be worth it but it'd be good to know which adjustments is speedometer hitting that harm the cache?
| Reporter | ||
Comment 7•11 days ago
|
||
(In reply to Emilio Cobos Álvarez [:emilio] from comment #5)
The two patches are fairly independent aren't they? They should be measured separately.
The second one seems probably worth it given how wide spread var() is. The first one I'm a lot less sure about.
I had tested only the var() separately and it did not give any improvement to the score. Apparently, the existing cache does not work well with var()
Comment 8•11 days ago
|
||
What aspect doesn't work (other than the fact that we currently mark those ineligible?)
| Reporter | ||
Updated•11 days ago
|
Comment 9•11 days ago
|
||
I guess the other question is "what part of the improvement is part 1 vs part 2".
| Reporter | ||
Comment 10•7 days ago
|
||
Cover root-relative lengths, shadow CSSOM and shared adopted stylesheets,
nested and dynamic variable references, and ruby/blockification adjustments.
Keep the original cross-traversal and variable-sharing tests together here;
the resolution preference coverage uses Gecko-only mochitest APIs.
| Reporter | ||
Comment 11•1 day ago
•
|
||
SP3 numbers:
Only Patch1
Only Patch2 (rebased to depend on the existing rule-node cache)
So atleast in its current form, we need patch1+Patch2 to get 0.6% SP3 improvement.
| Reporter | ||
Comment 12•1 day ago
•
|
||
| Reporter | ||
Comment 13•21 hours ago
|
||
The cached styles track the viewport size they depend on, so a media
feature change that only affects the viewport size, or which rules match
(size mode, display mode), doesn't need to empty the cache. Other changes,
like resolution, zoom or system metrics ones, still do.
| Reporter | ||
Comment 14•21 hours ago
|
||
A reset property set to inherit used to make the style uncacheable. Record
instead which reset structs of the parent it was inherited from, and only
reuse the cached structs for a parent with the very same structs. Parents
with the same rules commonly share them, since their reset structs come from
the rule cache too.
That makes the styles of anonymous boxes like ::-moz-scrolled-content and
the table wrapper cacheable, since ua.css inherits many reset properties into
them. Those are computed lazily outside of traversals, so give the stylist a
rule cache for them, which is merged into the persistent one along with the
traversal caches.
Copy INHERITS_RESET_STYLE from the cached style, so that restyles still
recascade children that inherit reset properties.
| Reporter | ||
Comment 15•21 hours ago
|
||
Elements that declare custom properties recompute them on every cascade,
including the var() substitution and cycle detection, even when another
element with the same rules and the same parent custom properties computed
them already. Pages with design tokens declare many of them per element.
Cache the result in the rule cache, keyed by the exact rule node, along with
what it depends on besides the rules:
- The custom properties of the parent, compared by pointer, since elements
share the custom properties of their ancestors by reference. - The registered custom properties, through a generation that changes
whenever they might have. - The device values that typed registered properties used (the viewport
size, the root element values), recorded the same way as for reset
properties, and the parent zoom. - The values of the element's own prioritary properties (font-size,
line-height, color-scheme, writing-mode, zoom), when typed registered
properties used them. Those are determined by the same rules and the
parent style, so the parent then needs to have the very same style
structs. - Reset properties inherited from the parent, as in the previous part.
- The values of the attributes that attr() read. They're queried again
when looking up the entry, which also keeps the style depending on them.
To record these, the cascade now records dependencies through
RuleCacheConditions::record, which also reaches the custom property
dependencies while they're computed. Recording a different value for the
same dependency makes the style uncacheable, instead of asserting.
On a hit, the custom properties, the attributes resolved by attr(), the
properties made invalid by cycles and the dependency flags are copied, and
only the prioritary properties are applied. The prioritary properties not
needed by any custom property are now applied after finishing the custom
property maps, which doesn't change their values.
| Reporter | ||
Comment 16•21 hours ago
|
||
The rule cache only reuses reset structs, so the inherited properties of an
element are cascaded again even when an element with the same rules and a
parent with the same inherited values computed them already.
The cascade of a style is determined by its rules, the inherited structs,
custom properties, zoom and writing mode of its parent, the flags it starts
with, the cascade flags, the pseudo-element, the registered custom
properties, and what was recorded while computing it: the device values it
used, the reset structs it inherited from its parent, and the attributes it
read. Cache whole styles keyed by their exact rule node with all of that,
comparing the parent structs and custom properties by pointer. Parents
commonly share them, and reusing whole styles makes that more common, since
the elements that reuse a style share its inherited structs too.
The StyleAdjuster only modifies reset structs (which are saved from before
the adjustment, as in part 3), the style flags and the writing mode, so the
entry keeps those two from before the adjustment, along with the rest of the
state of the cascade that the adjuster and the visited style use. On a hit,
no declaration is cascaded; the visited style and the adjustments are still
computed.
A style that reused cached reset structs or custom properties can still be
cached as a whole: the dependencies of the reused values are recorded along
with the ones of the style.
Raise the maximum number of persistent entries to account for the three
kinds of entries.
Updated•21 hours ago
|
| Reporter | ||
Comment 17•21 hours ago
|
||
Emits a PersistentRuleCache text marker on the main thread after each merge, with the cache size and what happened to the offered entries, to size MAX_PERSISTENT_ENTRIES and MAX_ENTRIES_PER_RULE_NODE from a Speedometer 3 profile.
| Reporter | ||
Comment 18•12 hours ago
|
||
Most entries of the thread-local rule caches never make it into the
persistent cache: in a Speedometer 3 profile, 72% of them have a rule path
that can't be kept across traversals (style attributes, animations,
presentational hints), and most of the rest have an equivalent entry
already. The main thread checked and freed all of them while merging, which
took about a quarter as long as cascading on the main thread. Do that before
stashing the caches instead, in parallel on the style threads for traversals
with enough entries, since it only reads the persistent cache. That also
frees the dropped entries on those threads.
Entries were also only ever removed by a sweep once the whole cache was
full, and a sweep evicted every entry not used since the previous one, so
two sweeps in a row could empty a full cache. Meanwhile, entries that no
style can match anymore kept their rule node's slots and the memory of the
styles they reference:
- Age entries by the number of merges since a lookup last returned them,
and make a sweep evict the entries that can never match, and then the
least recently used quarter of the others, never the ones used since the
previous merge. - An entry can never match once nothing but the cache holds one of the
values it compares by pointer (the parent's structs and custom
properties). Drop those whenever their rule node gets new entries. - When a rule node is full, replace its least recently used entry rather
than dropping the new one. - Report the memory that only the entries keep alive, including the styles
that no element uses anymore.
Updated•12 hours ago
|
Description
•