This year I worked on a project which involved populating a huge HTML table (up to 1,048,576 rows by 16,384 columns) cell-by-cell, with data retrieved via ajax calls. Needless to say, performance was not good.
But as poorly as I had expected the page to perform, it was even worse than I could have imagined. I could actually see the progress across the screen, step by plodding step.
The first thing I did was fire up Chrome Dev Tools. Obviously Webkit rendering is going to differ from Gecko, which is going to differ from IE, and so on, but many of the principles to speed up rendering are generally applicable.
Today we’re concerned with the Timeline tab, and particularly the Events and Frames timelines.
If you want to follow along with your own poorly-performing app, press Record at the bottom of the timeline panel and interact with your app in a way that causes delays or unperformant behavior. Then press record again to stop recording. You can now look at your recorded data either in a Frames view or an Events view, by toggling back and forth between them in the upper-left-hand corner of the Timeline panel.
The Frames view is most appropriate for identifying the cause of jerky or lagging animation. The main panel is divided into columns, each of which represents a “frame” or a screen redraw. The upper timeline panel shows two horizontal threshold lines serve as a guide for how efficiently your application is being laid out and drawn. If the duration of the frame (represented by the height of a rectangle on the timeline) peeks up over the 60fps line, it’s less than ideal. If it exceeds the 30fps line, there may be visible hiccups.
My concern was less with real-time rendering than it was with total rendering time, however. So I went to the Events panel. Here you can see the view from 10,000 feet — a quick summary of the duration and frequency of different types of events (rendering events are denoted by purple). You can also expand a particular event to see the sub-events that it is composed of.
What I found, in perusing the Events panel, was that not only was I rendering each cell in sequence, but that each such rendering event was accompanied by a bunch of other rendering events, which were eating up time, and for which I couldn’t account.
But what causes reflow? As you might expect, adding new elements to the DOM, or changing the geometry of existing elements will do it. But there are a number of other causes of reflow which are not self-evident. My most surprising discovery was the fact that measuring properties (by using offsetHeight and the like — calculated properties) is just as costly as setting them. Both trigger a reflow. What we have here controverts the old carpenter’s rule of thumb: measure once, cut twice. What I needed to do was store values for dimensions that weren’t going to change, instead of reading them from the DOM. With that simple piece of knowledge, I was able to reduce my rendering time to about a tenth of what it had been.