Design, CG Graphics & Inspiration
Optimization story

Optimization story

At that time there were a lot of such projects and to stand out from the crowd we decided to change the business model: instead of one offer a day to issue four. But, as usual, the appetite comes with eating, so after only a few months on the front page there were splashed 30 offers instead of 4.

Very soon people started complaining about the terrible deceleration of the front page. It took me two days to find and eliminate the problems. Further story is about finding the bottlenecks. At the same time we will learn how to use such tools as the Web Inspector’s Timeline (if you haven’t mastered it yet).

Problem search

So, we were faced with the fact that our main page is too slow. Source of the problem was found immediately: it was all about the animated timer in each offer:

* Five coupons already sold
Time remained before the end of sales 3 days, 15:20:40

The most evident problems have been observed in Firefox: CPU load on the main page reached up to 70%. So I began to examine timer’s script under a microscope, namely, the Web Inspector, which by default is a part of the Safari and Chrome browsers. In general, a lot of guys think rather condescendingly of this tool, and as a habit they continue working in Firebug, but all this in vain. For me personally, Web Inspector became a major tool for debugging: it looks nicer and has a number of useful innovations.

Let’s explore the bottlenecks

As the timer’s script is pretty simple, there was no sense in its profiling – it is clear that the problem is somewhere in the reflow and repaint. Therefore, the script must be explored through the Timeline:

I suppose that many of you have never worked with this tool, so I’ll describe the principle of its work and troubleshooting in a small lesson. It is worth noting that the Web Inspector in Chrome is bit cooler than in Safari, so I recommend using the first browser.

Timeline shows us almost all the processes that occur in the browser: running a script, debugging event, repaint screen, setting a timer, sending the Ajax request, etc. As there is a lot of data and it can easily get confused, I recommend isolating the script under the test – to allocate it to a separate page. For some practice, you can start with a simple template on which we will experiment.

Open the template in the browser and start the Web Inspector, Timeline tab. The page has a red box and «Test» button. To begin the research, you must press the record button of the Timeline tab and then click on «Test» in the main window of your browser. Our square turned blue and became higher and the following events were recorded in the Timeline:

The first three records refer directly to the button that you clicked: applied pseudo-class: active (Recalculate style), reflected the changes on the screen (Paint), got back the button to its original state by removing: active (Recalculate style). After the user released the mouse button the event click worked, and that’s it, what we will be interested in.

The following script worked during the click:

function test() {
var el = document.getElementById('test');
el.style.backgroundColor = 'blue';
el.style.height = '100px';
}

Nothing special: just received a link to the element and changed its background color and height. This process was displayed on the timeline: recalculated styles (Recalculate style), recalculated object’s geometry (Layout) and reflected the changes (Paint).

As you can see, despite the fact that we changed two CSS-properties, recalculation of the styles occurred only once. Let’s change the script:

function test() {
el.style.backgroundColor = 'blue';
var height = el.offsetHeight;
el.style.width = '100px';
}

Between the assignments of new styles, we decided to get the height of the object. But the timeline has changed greatly:

Now we have two Recalculate style events, but the click event has got a grouping (triangle to the left of the yellow stripe), which indicates what events occurred during the click.

This small example points out two important features of the browser – it is the delaying of repaint at the time of exiting the function (first example) and the existence of certain properties of the element that cause the forced recalculation of the styles (the restyle; second example).

I think many of you already know about the existence of the special properties that cause restyle: these properties are offsetLeft / Right / Width / Height, clientLeft / Right / Width / Height and so on. In the second example, after setting the backgroundColor property browser marked the elements’ tree as if it requires a recalculation of styles.

Hence the first rule: you should try not to confuse the receiving and recording CSS properties. For example, it is better firs to obtain the necessary properties of the element, and then set new ones.

Here is the example for jQuery fans:

function test() {
var e = $('#test');
var width = e.css('width');
if (width == '50px')
e.css('width', '100px');

var height = e.css('height');
if (height == '50px')
e.css('height', '100px');
}

Here is the scale:

As you can see, in addition to extra Recalculate style appeared Layout (reflow), which makes the script slower. If we’ll optimize it a bit by moving the height receiving above in the code:

function test() {
var e = $('#test');
var width = e.css('width'),
height = e.css('height');

if (width == '50px')
e.css('width', '100px');

if (height == '50px')
e.css('height', '100px');
}

… we will get something absolutely different:

Excess Layout (besides Recalculate style) happens each time when jQuery receives CSS properties window.getComputedStyle () was activated , which forces the activation of reflow. It is fair to note that there is an optimization in jQuery.css () function, which first checks the requested properties in element.style and if it isn’t there, then it activates window.getComputedStyle (). But in any case, it is always better to separate reading and changing of properties.

Timers

Let me remind you that I was making the optimization of few timers. Each timer is running through your setTimeout (). Let’s see what this means in practice:

function test() {
var el = document.getElementById('test');
setTimeout(function(){
el.style.backgroundColor = 'blue';
}, 10);
setTimeout(function(){
el.style.width = '100px';
}, 10);
}

Both timers have the same waiting period and the time of execution. On the scale we can see that Recalculate style was activated after each timer . But in reality, the time of execution is not always the same. Therefore, we change the delay in the last timer – will put 11 ms instead of 10 ms:

function test() {
var el = document.getElementById('test');
setTimeout(function(){
el.style.backgroundColor = 'blue';
}, 10);
setTimeout(function(){
el.style.width = '100px';
}, 11);
}

As we can see, the additional repaint appeared on the scale:

The result is that due to several running setTimeout () the unnecessary repaints that the user cannot see were activated, but the processor was loaded. I rewrote the timers’ code so that everything could work through a global timeout.

Summarizing everything I have done for the optimization:
• Separated reading and recording of CSS properties;
• Additionally made the caching of the current values of the animation, to pay less attention to the elements;
• Replaced a few time-outs with one.

As a result, the load on CPU in Firefox has decreased … only for 10%. Generally, it was extremely strange: even if there was only one animated timer on the page Firefox loaded CPU at 60%, while the Webkit loaded only 5%. It is nessecary to dig further.

How layout affects the performance

Two years ago I did a lot of research on how the layout affects the performance of the browser. Looks like my problem has similar roots. So I begun to go through all the ways of optimization that I know. But, unfortunately, nothing helped.

As I have already optimized all the restyle and reflow processes, the problem was clearly somewhere in the repaint. Then I remembered that in the event mozAfterRepaint appeared in Firefox 3.5, which allows you to see areas that were repainted during the repaint. For convenience, the Firebug Paint Events extension was added. It allows you to monitor the repaints of the screen.

To share the emotions that I experienced after watching the logs, I suggest the reader to look at the screenshot, which shows the area of repainting during the work of only one timer on the page:

Even during the animation of a single timer approximately 90% of the page was repainted 15 times a second. And it is provided that in timer’s numbers position: absolute was specified, and at their container overflow: hidden. That means that the animation by definition itself could not affect the area outside of the container (the screenshot is marked in blue rectangle), but almost the whole page was repainted.

It took me about an hour to find the reason for this strange behavior. And it was … the property float: left from one of the containers. As soon as I replaced it on the float: none CPU load was falling below 10% (with a float: left it was about 60%).

The problem shows itself stable, not only in Firefox, but in Opera and IE8. I made a simple demo, where you can see this problem live. It has only a few blocks, but there is a box-shadow-a very heavy CSS property in terms of CPU load. In the upper right corner there is a button that only switches float in a container. Watch the load on the processor at different button states, as well as repainting area.

In general, the problem can be described in this way:

Repaint is being activated on the container of the most distant parent who has indicated float: left | right.

Schematically it looks like this:

And the problem is not only in the float. I have tried various options of horizontal grouping of blocks: display: inline-block, display: table-cell, tables and even new-fangled flex box – the problem remained in all cases. Only the absolute positioning of lateral blocks helped.

In general, I’ve solved this problem: put the sidebar in the code before the main container and only then indicated the float. The main container was without a float and repaint occurred precisely where it had to be. However, the problem on a live site wasn’t solved, and as most of the pages were clearfix elements, because of which the design was falling. So yet we had to turn off the animation timer.

Source

  • Splashnology Editors,
  • April 28, 2011

SHARE THIS POST

This post has been written by the team here at Splashnology.com

Subscribe for the hottest posts

Subscribe to our email newsletter for useful tips and freebies.

  • mn

    n

  • mn

    n