Optimizing the size of WidgetState
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 5.5k
- Forks
- 240
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 4
Description
Right now WidgetState is a pretty chunky struct. VSCode with Rust Analyzer currently tells us:
// size = 296 (0x128), align = 0x8
Yeah. Three and a half widgets are enough to fill a kilobyte. Your usual L1 cache is about 64KB (from a quick Wikipedia check), which is 216 WidgetStates.
That's probably pretty bad. A lot of realistic applications have more than two hundred widgets, and we don't want them to overflow the L1 cache. (For reference, this github issue after two answers has 2467 html elements.)
What can we do to reduce the size? Some ideas:
Bitflags
There are 26 boolean flags.
They take 26 bytes, which we could get down to 4 bytes with bitflags.
However, bitflags are more annoying to use that booleans, and switching to bitflags would only be enough for a <10% size reduction.
Replace f64 values with f32s
We use a lot of f64 types:
size: 2.origin: 2window_origin: 2paint_insets: 4local_paint_rect: 4baseline_offset: 1ime_area: 4clip_path: 4translation: 2
That's a total of 25 f64 values, which is 200 bytes, two thirds of our size.
Realistically, virtually all of them could be replaced with f32s with almost no code change, saving us about 100 bytes.
Most of them deal with local coordinates where we expect that most of the range of f64s will be wasted. A widget is never gonna have billions of pixels of paint insets, for instance.
If we ever decide to switch to fractional coordinates, we could store a lot of them as u16, which would be yet more size savings.
Optional fields
The fields ime_area and clip_path are optional. Moreover, fields like paint_insets, baseline_offset and translation aren't optional, but their value is almost always zero.
(cursor too, but the type itself is very lightweight.)
We could store these fields in an Option<Box<...>>, and save memory for 90% of widgets.
Struct-of-array
The most radical change would be to store WidgetStates in a struct-of-arrays pattern.
The problem with WidgetState right now is that a single instance occupies multiple cache lines. That means that accessing two widget states is always going to require touching multiple cache lines.
If we could instead store WidgetStates by field, the cruft would be a lot less of a problem: code iterating over flags would keep the flag array in L1 and wouldn't care that some massive amount of layout bytes is being kept in main memory somewhere.
Switching to struct-of-arrays would be a very ambitious change, probably requiring some radical improvements to the architecture of TreeArena. It would probably impact every single line of code currently using WidgetState.
Benchmark?
Before we do any of that, we probably want to check that Masonry's memory usage is actually bad, is actually bottlenecked on WidgetState, and that L1 misses have an actual impact on our performance. I'm not sure how we'd check that, suggestions are welcome.
I don't expect us to address these problems overnight. We should expect this to be a long-term issue.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating WidgetState and TreeArena and measuring Masonry's memory use, WidgetState's contribution, and the effect of L1 misses. The issue does not name files or tests; done would require benchmark evidence and a decided optimization direction before undertaking the broader change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- frontend, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100