-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incremental layout in 2020 #25168
Comments
Yes, basically my idea lets us keep the children of a The end result of what I have in mind is that we incremental box generation would then be able to start from a list of root elements which were modified since last layout, and parents would only need to be reconstructed in a limited set of circumstances (for example, a previously-unsplit inline box in which a block box is inserted would trigger reconstructing its parent boxes until the nearest enclosing block box, but further edits to the split inline box wouldn't reconstruct parents anymore), AFAIK that gives us amortised |
I don’t understand this part, could you rephrase? |
Some things straight out of my mind: An element that generates an unsplit inline-level box would be handled just as currently, storing an
Note how the inline-level child generates exactly one On the other end, if that eleement generates a split inline-level box, we would store a value of a type like this instead: struct SplitInlineLevelBox {
leading_inline_level_box: Arc<AtomicRefCell<InlineLevelBox>>,
inner_block_level_box: Arc<AtomicRefCell<BlockLevelBox>>,
trailing_inline_level_box: Arc<AtomicRefCell<InlineLevelBox>>,
} And on creating the box for the parent, two different things can happen again: Either the parent is a block box, and we need to do three things:
The resulting layout box once every child has been handled is an Or the parent is an inline-level box, and we need to do three things:
The resulting layout box is a |
Do we actually need reference counting here? When we go down the box tree to perform layout, we could keep the old render tree from the previous layout around and follow the links down through clean nodes with dirty descendants. Then when we get to a clean node with a clean descendant subtree, we just extract the old render subtree and use that instead of recreating it. Everything remains singly-owned at all times. |
We need reference counting whether or not there is incremental layout going on. Each box points to its children boxes, and each DOM element points to its layout box(es) too. |
What about using A fallback plan if the |
We still own those from 2 different places at once, so |
So when you have 2 references to the same layout tree box from its DOM element and from its parent layout tree box, you have two choices. Either you call Or you call |
As compared to R/W locks around every box? |
I don't think anyone of us expect to use R/W locks, just |
Note also that |
You still think pervasive use of |
AFAIK no, we never said it was infeasible. My comment just before yours mentions |
Well, that's good news then :) My understanding per jdm was that there were doubts about it, but if not then carry on. |
As a potential backup plan if Another interesting avenue to consider is lock elision/hardware transactional memory. I could imagine something like "if hardware lock elision is available, then always do layout in parallel; otherwise, sometimes perform sequential incremental layout based on heuristics." |
Started a branch for |
So are you working on this Patrick? The last thing I want is to duplicate efforts. |
A very first step towards incremental layout would be to skip box tree construction when none of We need various things for that:
|
(Also of course, we can't skip it if the document tree itself changed, e.g. when elements were added or removed.) |
I'm not really intending to work in depth on incremental layout right now. I did #25957 primarily for performance testing. I submitted it because the perf results are positive and it's a fair bit of work so I wanted to save y'all the time of having to make those changes :) |
Another good one is to skip layout entirely if only paint-relevant properties have changed. This covers a lot of common situations; e.g. mousing over links and selecting text. |
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
So I've looked into the current damage system. The stuff in The problem is that this system is really dependent on how layout is done, and thus the different categories of damages correspond to layout 2013 so much that I don't think it can be leveraged for layout 2020. Do we want a new damage system that is specific to which layout system we are using instead? Like, does |
I agree with that; let's make a new damage system. I don't have a lot of love for the old one anyway. |
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
Good question! One thing I'm not sure about is how we update the styles of the trees we keep to regenerate the later trees that we don't keep. Do we just mutably borrow each box and fragment to change their style field, or do we store the |
|
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
Start using `AtomicRefCell` in layout 2020 as preparation for incremental layout This makes `BlockLevelBox` and `InlineLevelBox` use `AtomicRefCell` for incremental layout, per @nox's suggestion in #25168. As part of this, it reworks inline layout to use recursion, per #25950. LLVM should be able to optimize this into a loop (though I have not verified this). r? @nox
In our Mako files, |
And |
The inputs of rendering includes the DOM tree, stylesheets, form data, input state (
:hover
, …), scroll positions, etc. When any of these inputs changes, we need to re-render a push a new bunch of pixels to the screen. It order to make this cheap, we want to reuse as much as possible of the previous rendering and skip re-computing intermediate results that haven’t changed. For layout in particular, this means reusing parts of the CSS box tree and fragment tree.At the same time, we want fine-grained parallelism during layout to take advantage of available CPU cores. Avoiding data races requires either avoiding or synchronizing any shared mutability.
During our explorations last spring, I settled on trying the following approach to reconcile these goals:
The box tree and fragment tree are each thread-safe and made of atomically reference-counted nodes.
Nodes are mostly read-only after construction. Some pieces of data may be in
AtomicRefCell
s (if the algorithm structure is such that concurrent access would be a bug) orMutex
es orRwLock
s, but most data can be read without synchronization.The trees are persistent: updates are done by creating a new tree that share existing nodes for some sub-trees. "Modifying" a single node involves creating new nodes for it and its ancestors up to the root. This overhead is hopefully viable since HTML trees (which box trees and fragment trees are based on) tend not to be very deep, although they can get wide.
Currently the child nodes of a given node are kept in a
Vec
. So creating a new node that is similar except with one different child node requires creating a newVec
, which takes O(n) time where n is the number of direct child nodes. It’s not uncommon for that number to grow large, so switching toim::Vector
could help:clone
is O(1) andget_mut
is O(log(n)).I think we could start with some conservative reuse in cases where we can easily determined that a subtree definitely hasn’t changed, and make it more fine-grained over time.
@nox, you mentioned an idea for reuse around block-in-inline splits?
The text was updated successfully, but these errors were encountered: