Show Potential Scroll bottlenecks / Scrolling Perf Issues
This feature is kind of black magic (to me, at least). Let's try to demystify it a bit.
void RenderWidgetCompositor::setShowScrollBottleneckRects(bool show) {
cc::LayerTreeDebugState debug_state = layer_tree_host_->debug_state();
debug_state.show_touch_event_handler_rects = show;
debug_state.show_wheel_event_handler_rects = show;
debug_state.show_non_fast_scrollable_rects = show;
layer_tree_host_->SetDebugState(debug_state);
}
https://code.google.com/p/chromium/codesearch#chromium/src/cc/debug/layer_tree_debug_state.cc&sq=package:chromium&type=cs
https://code.google.com/p/chromium/codesearch#chromium/src/content/renderer/gpu/render_widget_compositor.cc&sq=package:chromium&type=cs&q=RenderWidgetCompositor::setShowScrollBottleneckRects
to be continued…
https://plus.sandbox.google.com/+RickByers/posts/LXKEENhnQSD has some good details on it.
I've talked before about how touch event handlers can impact scrolling performance (https://plus.google.com/115788095648461403871/posts/cmzrtyBYPQc), and how compositor-thread touch hit testing helps mitigate this by keeping track of what regions of the page have touch event handlers. Now (http://crbug.com/253552) you can easily see where exactly those regions are by enabling this option in dev tools.
I'm in the process of overhauling how compositor touch hit testing works to make it more reliable and faster (eg. see http://crbug.com/248522). I apologize in advance if I break your website - there's a surprising number of corner cases (symptoms are that your touch event handlers won't get invoked when they should), but at least now you have an easy tool to prove that it's my fault. These "touch event listener" rectangles should always include any region that has a touch event handler (including descendants of nodes with handlers), and the rectangles are supposed to move precisely with the elements that have the handlers.
Check out http://jsbin.com/apudet/1 with "show potential scroll bottlenecks" enabled to see an example of where this is broken today (although it should be fixed by my change in the next Canary build or two).
Can't wait for that talk. I have been playing with this over the past week. We really need to document the causes of what it points out and alternatives/solutions.
For example Cartalyst docs it shows a mousewheel listener being a potential cause. However we don't seem to have any docs on why this is a bottleneck or how to solve it.
I'll watch that and take what we can for the docs. From there I'll try doing some research online for more detail.
Whoops, just realized my mobile Chrome messed up opening the link. :frowning: So, Paul Lewis's talk (what page Chrome opened to when I clicked the link from gmail on my phone) may not cover this point.
So I'll just start doing some research and see where it leads.
(just a few notes here)
primary usecases for show scroll bottlenecks
Currently these features help with two symptoms
- the page is forced into slow scrolling mode (main thread scrolling)
- the page is paint storming
The first is something to consider early, the second is something you see from lots of green in timeline and validate through Show Paint Rects.
Repaints on Scroll
Repaints on Scroll is one case I'm not sure I understand clearly.
That appears to improve the situation but not reliably. I think that action is on chrome to improve and believe our input-dev team is owning it:
- Issue 505523 - chromium - Need better visual scrollability signal when primary scrolling element is not the body https://code.google.com/p/chromium/issues/detail?id=505523
- Issue 505516 - chromium - Should be much easier to build full-page scrolling apps https://code.google.com/p/chromium/issues/detail?id=505516
more details
Lastly: https://code.google.com/p/chromium/issues/detail?id=253552#c13 is dated but still has helpful details on these items.
I noticed on touch enabled browsers, starting from chrome 46, that range inputs are potential perf bottlenecks. In particular when creating hundreds of them it seems the whole body gets the touch listener assigned, becoming the highlighted perf bottleneck and dramatically impairing performance compared to having touch events flag disabled.
Is there a clear logic to predict when touch inputs will get assigned to the body (given this is what's happening)? Is there some some documentation about how and why touch events impair performance so much?
I noticed coding this demo, which for sure is not a real world case to care about: http://paolocaminiti.github.io/incremental-dom-jsonml/demo/primer6/
@paolocaminiti oh interesting. good find!
Here's your demo with 100 input type=range's, and then again with 101 of them:

Looks like Chrome switches to event delegation internally when theres > 100 handlers. Pretty cool.
Documentation on how/why touch events impair performance:
- Debounce your input handlers | Web Fundamentals - Google Developers https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers?hl=en
- input latency thanks to cancelable DOM events https://docs.google.com/document/d/1fRUsu_3s7f0CRYeCcrmiDc6wrnwisuhH8Q9agFCSdsA/edit#
Thanks for the answer, it's great you are writing some draft documentation about it, and passive events seem a great proposal (I back linked this thread from the demo).
Would be great to document if the "100" auto optimization threshold is arbitrary or it varies for platform/device/performance combinations. And which events are subject to be optimized under which cirumstances (touch only?).
Anyway I'm a bit puzzled why the latency is kicking in here. While "animate" is on there should be no events firing, everything it does is applying some properties to a state object inside a setTimeout, then inside the next animationFrame they are picked up by the jsonml ui parser and under the hood incremental-dom will apply those with a setAttribute. At 60fps about < 10 items should be updated and the others should not go even trought the jsonml generation or incremental-dom traversal. I tried avoiding the assignment of the inputs .value but it makes no difference.
In the dev-tools timeline I can see everything stays the same between touch enable/disable except for "Update Render Tree" consuming something like 10x the time, which is empirically consistent with the fps dropdown: 500 items with touch running somewhat worst than 5000 without touch. I can see no extra calls into the js thread, also why should it care for cancel status of handlers if no event was fired at all?
It's hard to tell whether the delegation to body is co-responsible of the slow down, since at 101 items performance will be just fine, being an optimization I suppose no, but if there is a way to test it I would love to know it.
This magic '100' threshold is just an implementation detail around the tradeoffs between the cost of maintaining a bunch of fine-grained rectangles and the performance benefits of the optimization. IIRC the limit is per-layer, so if you can stick all your range sliders into a single DIV with 'will-change: transform' then you'll get a nice compromise (but don't bug EACH ranger slider in it's own layer, per-layer rendering costs can be high).
Note though that this perf optimization only really matters when you've got long-running JavaScript (or other main thread tasks). If it's "dramatically impairing performance" then there's probably another problem (try devtools timeline view).
And yes, once we have passive event listeners, internally we should be able to use 'touch-action: pan-y' on these range sliders and make their internal touch listeners passive, avoiding this problem entirely.
So from the timeline seems the huge time difference is consumed exclusively by "Update Layer Tree". No differences on the "Hit test" timing either.
I can see only one layer in the Layers tab details: #document.
When the touch delegation chimes in, the whole body (even out of the painted area) is highlighted as a slow scroll rect.
Even with no event firing and no scroll happening the Update Layer Tree phase is impaired by the slow scrolling region caused by uncalled touch listeners... I probably just don't know what the Update Layer Tree phase is taking care of, from the timeline it's quite a black box.
But now the funny part: will-change applied to each item restore about half performance with touch enable and cuts in half with touch disabled. I suppose this is due to now having many layers as you said, but for this demo that's the only way it would apply, putting it on the whole container div makes no difference at all.
I've included a high-level overview of the problem here, along with a partial solution we're working on.
Applying this simple rule on the body seems to restore performance.
touch-action: manipulation;
It even works on the shadow-dom of a webcomponents based version of the same demo, then i wonder why it's not the default if the sliders are internally implemented via shadow dom.
Right now I'm not able to do proper comparison and profiling, but fps seem completely restored.
I've made a screen capture explaining this feature and the related passive event listener features of dev tools.