Skip to content

Faster style v2#1063

Merged
jrmoulton merged 11 commits intolapce:mainfrom
charlescgs:style
Apr 11, 2026
Merged

Faster style v2#1063
jrmoulton merged 11 commits intolapce:mainfrom
charlescgs:style

Conversation

@charlescgs
Copy link
Copy Markdown
Contributor

This PR re-enables and fixes the style's cache, and adds few optimizations to reduce allocations and redundant work on the style hot path. Result is ~10-29% improvements on style benchmarks with no regressions.

Changes

1. apply_mut takes &Style instead of Style

Every call site was cloning Style just to pass ownership to apply_mut, which then only reads it. Changed the signature to eliminate ~15 unnecessary Style clones across the codebase

2. Cached StyleSelectors bitmask on Style

selectors() previously traversed the entire style map to check which selector types were present — called repeatedly during resolve_selectors. Added a cached_selectors: StyleSelectors field incrementally updated when selectors are added via apply_iter, set_selector, set_structural_selector, set_responsive_selector. Reduces selector presence checks from O(n) map traversal to O(1) bitmask read. (Correctness is being checked debug_assert! against the slow path in debug builds)

3. Per-frame timestamp

Instant::now() was called per-view during the style pass. Captured once at frame start and shared via WindowState::frame_start

4. FxHashMap

Replaced HashMap with FxHashMap for style cache

5. Content-based content_hash() implementations

The default StylePropValue::content_hash() used format!("{:?}", self) — allocating a String per property per hash. Added manual implementations for all ~40 concrete types using FxHasher with to_bits() for floats and std::mem::discriminant() for enums. Zero-allocation hashing throughout

6. Eq via PartialEq instead of hash comparison

StyleMapValue comparison was hashing both sides and comparing hashes. Changed to direct a == b via the existing PartialEq impl

7. XOR order-independent content_hash() on Style

The style-level content_hash() collected entries into a Vec, sorted by key pointer, then hashed sequentially with allocation. Replaced with XOR of independently-hashed entries: each entry gets its own FxHasher, and results are XOR'd together. O(n) -zero allocation (order-independent by construction)

8. Re-enabled style cache with fixes

  • Cache entries now store full compute_combined() outputs: combined_style, has_style_selectors, post_compute_combined_interaction
  • Replaced Rc<Style> wrapping with direct Style storage (the Rc indirection added overhead without benefit)
  • Added window_width_bits to cache key for responsive selectors with exact pixel thresholds
  • Added is_focus_within to interaction state bits
  • Proper cacheability checks: styles with structural selectors (:first-child, :nth-child) or context-dependent values are excluded
  • Cache cleared on theme change and screen size breakpoint change
  • Parent validation uses map_ptr() (inner Rc pointer) instead of outer Rc::as_ptr()

9. Cache-hit path avoids Style::clone()

On cache hit, the previous implementation cloned the full Style to build the cache key, then discarded it. Restructured the hot path:

  • StyleStack caches the content_hash as a side-effect of style() recomputation
  • Added ensure_clean() / content_hash() / is_cacheable() methods on StyleStack
  • Added style_content_hash() / style_is_cacheable() forwarding on ViewState
  • Cache key construction uses the cached hash via StyleCacheKey::new_from_hash() — no Style reference needed
  • On hit: directly assign cached outputs to ViewState fields, bypassing compute_combined() entirely

10. More tests coverage

Benchmark results (vs current main)

Benchmark Change
view_creation/empty build -29%
view_creation/empty mount -14%
view_creation/label mount -5.7%
identical styles/create+compute -10% to -16%
identical styles/restyle (cache hit) -22% to -26%
different styles/create+compute -8% to -14%
deep nesting/create+compute -6% to -21%
complex styles/create+compute -7% to -17%
style_clone_and_read -19.6%
inherited_prop_updates -1% to -7%
inherited_with_selectors -2%
inherited_stable_context -5%

@jrmoulton can you test this yourself?

@charlescgs charlescgs changed the title Faster styles v2 Faster style v2 Apr 7, 2026
@jrmoulton
Copy link
Copy Markdown
Collaborator

This looks good to me. I wish there was a more automatic way to do content hash but I do think that this is better than allocating/hashing a string.

@jrmoulton jrmoulton requested a review from dzhou121 April 8, 2026 04:55
@jrmoulton
Copy link
Copy Markdown
Collaborator

@charlescgs do you mean for this to still be a draft?

@charlescgs charlescgs marked this pull request as ready for review April 11, 2026 16:23
@charlescgs
Copy link
Copy Markdown
Contributor Author

No, added is as a draft so can gather feedback

@jrmoulton jrmoulton merged commit ae651b1 into lapce:main Apr 11, 2026
12 checks passed
@jrmoulton
Copy link
Copy Markdown
Collaborator

Thanks for doing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants