We spent a week making Trello boards load fast
blog.fogcreek.com
blog.fogcreek.com
Here's how I fixed it
1. I added pagination - Load only 300 cards, paginate to load more. We found that while some people had 300+ cards in their default view, most never actually utilised the view usually using a filter to bring down the no of cards to a more manageable level.
That still left about 3-6 seconds of page load time for 300 cards. Unacceptable. So these where the further steps I took to fix it.
2. The main culprit after some rudimentary profiling seemed to be the jQuery's .html('') tag. Apparently it does some cleanup which takes awhile on a huge DOM block with lots of attached events. Replaced it with by looping through each child node in the DOM block and removing them with removeChild. Achieved 8x speedup, loading time down to 1-2 seconds with 300 cards.
3. Second culprit was the jQuery UI's droppable and draggable initiations was taking awhile. Hacked around this by putting the jQuery UI's droppable and draggable initiations inside a setTimeout thats fired in 100ms. Of course, this doesn't affect the actual rendering time but the perceived rendering time is now <1sec because the pipeline loads instantly and it is usually 1-2 seconds before the user does an action on the UI.
All in all, just less than <20 lines of code changed but took me two full days to figure it out. At one point, I was looking through jQuery UI's droppable code and futility seeing how I could optimize it. :)
Avoid JQuery and use JavaScript 5 native API, if it's reasonable target (no IE8 < 9).
Check out http://jsperf.com/popular and use fast (and sane) methods.
btw. JQuery 2 dropped support for such older IE version too
It's important to let jQuery clean up its event handlers, otherwise you'll end up with memory leaks.
In fairness, getting really good dom performance out of Backbone is hard because of the lack of built-in delegated event support and the non-synchronized redraws. If you have big apps that need to be super-fast, either read up on dom performance practices and adopt some helper libraries along with Backbone, or try a framework that has its own draw cycle and delegated event support.
[1] http://lostechies.com/derickbailey/2011/10/11/backbone-js-ge...
It actually is builtin: https://github.com/jashkenas/backbone/blob/1.1.0/backbone.js...
In fact it takes a bit of effort to not use event delegation when setting up your events dict.
Wait I'm getting confused. Didn't the title post and your linked article both say not to do this if performance is the goal? (See "Tuesday" in OP). Isn't that what we're talking about here? Sorry if I misunderstood.
1. Don't render all of them at once, use pagination or infinite scroll.
2. Don't attach dom events to each item individually, instead attaching one event listener to the list container and having a way to go from the event's target to the appropriate view to act on.
3. Don't even create a view object for each item, just render them with the same template and have a composite view that knows how to act on each item's model for the delegated events.
It's important to understand the relative performance benefits of these changes. #1 will make things faster in proportion to how much the page size is smaller the the full list size. #2 will reduce the number of DOM calls by a factor of the number of items in the list. #3 will reduce the number of JavaScript objects created. #2 gets you a 10x-100x bigger speedup than #3. JavaScript fast, DOM slow.
Most of the time (IIRC 60-70% for ~20 events) is spent registering DOM event handlers. Instantiating the view itself is nothing in comparison.
So registering all the events on the container and delegating (in the POO meaning not DOM) give you a very good performance without scarifying too much your code complexity.
I worked on a rather large extjs app, and IE was particularly bad about cleaning up dom objects with events attached, leading to memory leaks so bad that IE <= 8 would need to be reloaded a couple times a day. Firefox at the time was much better, and chrome was really new (so was IE8 at the time).
jQuery is good about keeping track of events and properly cleaning up, but it is expensive... though it's easy enough to .find('> *') and detach them, pushing those items into a cleanup queue.
This method is used in game dev a lot.
I think the more expensive cost is cleanup, or lack of proper cleanup leading to memory leaks... I'll usually detach nodes to cleanup, and put them into a queue that's run in a setTimeout loop... that way they don't slow down the UI.
Also note that he says "Perceived rendering went down to 960ms". So this is largely done to alter perceptions anyway, not necessarily total throughput or however you'd like to phrase it.
It might be a win if the thing you apply it to:
1. Never changes, but the content around it changes often.
2. Is hard to render (lots of shadows, etc).
The layout and paint thrashing is a really good optimization though. You should be able to insert as many things into the DOM as you like without triggering a layout SO long as you don't read back (like consulting offsetLeft). I think the Chrome inspector will mark read backs with a little exclamation point in the timeline with a tooltip "synchronous layout forced" and a backtrace to your JS...
The same thing works with all of the other 3d transforms: Putting in a BS value for Z will cause the element to use hardware acceleration.
The content of the layer isn't hardware rendered. It's rendered by the CPU and uploaded to a texture. In WebKit and probably Blink there's a fast path for images, canvas and video so that they can be directly uploaded or (on some platforms like Mac) bound to a texture avoiding an upload copy.
Microsoft and (maybe) Mozilla have a "hardware rendering" path via Direct2D, but Chrome and WebKit don't, they have compositors which can use the graphics hardware to perform compositing, but not rendering.
That hardware rendering is smoother is also not true in general, just some cases, which the browser will try to guess for you.
Ember.JS (and possibly Angular?) does this for you automatically.
Dirty checking and watch execution happens in batches, but the resulting DOM updates are executed ad-hoc within each batch iteration - without any regard for requestAnimationFrame or forced synchronous layouts for instance.
Other than allowing the browser to paint in the middle, I'd say it's equally (if not more) important that the _.defer calls allow user events to interleave rendering, so you get a bit of scrolling, clicking, hovering, etc. Not doing so is akin to running an intensive operation in the UI thread (for those coming from Swing or Android), and you get a frozen browser page instead.
The one caveat we've seen, though, is your code gets more complicated due to the async rendering. For us the async render was just a subcall in a larger render method, and some later calls relied on the async rendering being complete for some measurement purposes. We had to move those calls to a callback after the queued rendering was done, but ideally only wanted SOME of it to be deferred (some click handlers, etc, we wanted set up earlier so the user could interact with the page), but in a larger codebase you get into a refactoring nightmare, etc etc.
All being said, though, it was probably worth it. :)
You can still read it if you need[0]. Theoretically, major layout invalidation should only be done in bulk during reconciliation, but I don't know when the rendering phase applies, and React also has component-local state.
I know Om[1] only does rendering on requestAnimationFrame (so at once and synchronised with RAF) but that seems to be done by Om itself[2] and I can't find any clear documentation on that part for React.
[0] although it may not be up to date if you're between a state change and a rendering
[1] https://github.com/swannodette/om
[2] https://github.com/swannodette/om/blob/master/src/om/core.cl...
If you're doing complicated layout code, then you will need to read from the DOM. Currently React doesn't have great support for managing layout from JS in a clean, efficient way but it's one of the things we're looking to add in the future.
Oh absolutely, sorry if that was not clear.
But sometimes you need to (e.g. to place an overlay or tooltip thing over an element), and AFAIK that remains possible.
But as you can see, the documentation is a bit sparse ;)
The reason is that CSS layout is exceedingly complex and all of the elements within or even across container boundaries can influence the position and appearance of other elements.
If they did their own custom layout using absolute positioning + JavaScript, then they could easily create "virtualize" the items in the container, only rendering those that are currently visible.
This is a major reason that I'm a believer in the React.js or other "Immediate Mode UI" models. This is how games have worked forever: You simply query your world state for "stuff currently in view" and draw that as fast as you can, caching anything that change infrequently and is expensive to recompute.
Just imagine Qt markup rendered by your browser and glued by Javascript.
Actually, if I understand it correctly, famo.us does this / tries to does it. For the past couple of years, actually; it could be a hoax by now. IIRC, they want to make things public somewhere this year. It's just a tech demo so far though, which has already been recreated with three.js
It seems like those guys have much more fun now more of the cross browser pain has been abstracted away.
One positive is the lack of old IE version support these days. But then there is also mobile.
Also, I understand why things get slow, but I will never understand why performance benchmarking doesn't seem to exist in many places as part of the QA process. Writing tests and making things work right is usually there, but making sure things are performant and the user has an outstanding experience seems to get left until it's a "problem".
pre-rendering the board on the server would have solved his perceived problem immediately.
then in more detail:
layout thrashing only now a consideration? (advice use a mock dom and see what your operations do in your testing if you decide to handle dom manipulation yourself)
as a developer you only started using the profiler when?
too many http request, can be much optimised (yes i realise cdn, but ttl there can be managed nicely even for a single delivery)
css not remaned and compressed
own js badly minified
using jquery ffs!
>> anyway, perceived rendering would have solved this by the metrics solved is measured here and simply rendering on the server and giving a 500ms ttl on the cdn would have been faster + not overburden their servers. I don't know their stack so perhaps the next staement is useless: is this api with the big taskboard open aka can I have a stab at it and try to explain and proof what I am talking about.
I'm just getting started with using RAF for some JS animations I want to be very high performance, but haven't seen what impact it would have on something as large as a huge trello board.
RAF performance may also be more variable between browsers than simply reducing layout thrashing. At this point though I'm speculating. Would be good if someone more knowledgable would do a comparison.
The reason layout thrashing happens is due to cached layout metrics being invalidated, causing information to be re-computed over and over.
But the layout cache isn't global for the page. Browsers do their best to not invalidate cached layout metrics unnecessarily.
For example, an element's height often depends on its width (due to wrapping of text and other inlines). That height can be expensive to compute because it requires layout and word wrapping of all the element's children. But if you move the element to a different container, but the element's width and cascaded/inherited styles stay the same, some browsers will not invalidate the element's height. (Check out how fast the "Reparent" test is on http://jsperf.com/are-reflows-created-equal in Safari and Chrome )
So if you find yourself in a situation where layout thrashing is hitting you hard, try to find ways to give the browser more explicit information about your layout, so that layout cache invalidations don't propagate as far. For example, giving parent elements an absolute width and/or height can help a lot.
This way, you can often eke out the performance you need, while avoiding heavy-handed refactoring necessary to always batch DOM changes. (Unfortunately, you'll need to verify the improved performance in all major browsers -- not all will have the same optimizations. It would be great if browser vendors documented their behavior more!)
The project is a NodeJS implementation of the Chromium telemetry smoothness and loading benchmarks and the data from it could check perf regressions.
I could help with the integration if needed.
I don't like the big icons. It takes longer for me to read the same amount of information. Don't take my word for it, but there you go.
Is there anything like a standalone version we can run in a large multinational? External cloud-services are no-go for legal reasons. It's really annoying me that my wife and evening-work colleagues are super-sophisticated with kanban and then I come in to work with a shitty to-do spreadsheet.
In your code it’s mostly adding DOM children (invalidating the layout) and getting an offset later on (thus forcing the layout) for ca 25 times. The page should be much more responsive.
[0] https://developers.google.com/chrome-developer-tools/docs/de...
https://developer.mozilla.org/en/docs/Web/API/DocumentFragme...
I found this technique to make major improvements for a similar task. This only makes sence once rendering is a big bottleneck of course, you don't want to over-parallelize.