blog.smart-java.nl
Ordina J-Technologies – Java Blog



Client side performance tuning: Minimize HTTP requests

By: Jan-Kees van Andel, 23 August 2009

For some reason, when talking about web site/application performance, people think about the server side, like optimizing database queries and tuning connection pools. Yeah, of course, it’s important to tune the server side. But what people often don’t realize is that there’s lots to gain on the client side as well.

For the people that haven’t done so already, download Firefox with the Firebug plugin. You can activate Firebug using the F12 keyword.

Firebug has a really useful “Net” tab (which must be activated because it has quite an overhead. You can use this tab for debugging (inspecting headers and stuff), but it also gives you a good indication of your performance.

To give you an example, below is a screenshot of java.sun.com.

firebug_net_java_sun_com_thumb

Analyzing the problem

What can a developer learn from this output?

  1. First, you can see that the server side part is quite fast. It only takes 220ms to write the entire page to the client (the first bar). They could maybe optimize a bit by flushing earlier, to have better network utilization, but this is probably the consequence of an architectural choice (I assume they’re using an MVC style approach like Struts, where you would first gather all data and then forward to a JSP page). It wouldn’t even be a big win, since the server side performance is only a small part of the total performance.
  2. Second, we can see a lot of activity is happening after the initial page request. By using the handy Firebug filters, I can easily see that 3 CSS style sheets, 17 JavaScripts and 38 images are fetched. This sums up to a total page load time of almost 4 seconds. In fact, there are so many requests that they don’t even fit on my screen.
  3. A third lesson we can learn from this output is that almost all requests are made sequentially. I’ve made a simple calculation. The total amount of data downloaded is 137KB and it took around 4 seconds. That’s around 35KB/s!!! As an indication, I normally download (large files) with a megabyte per second. How about bad network utilization!
  4. Fourth, there are some moments where no resources are downloaded at all. This is because downloaded resources have to be parsed, executed, rendered or whatever. This also hurts parallelism. After all, the computations required to do something with a resource don’t involve network I/O, so it would be nice if the browser started downloading the next file. This would be way more efficient. Fixing this issue is not trivial in most cases, so I’m not gonna talk about it here. Steve Souders has blogged about it extensively.

Http connections in your browser

Before trying to fix the problem (which I’m not going to do, since it’s not my website), we need to know some things about how web browsers handle requests.

An important thing to know is that browsers only use a certain amount of connections in parallel to the same web server. With web server, I mean the same IP address. So if multiple domain names resolve to the same IP address I’m talking about the same web server. This is in accordance with the HTTP 1.1 protocol, which states that a single web browser instance should only use 2 connections per web server. Below is a table with some popular browsers and their default parallel connection counts, using HTTP 1.1. (when using HTTP 1.0, the numbers may differ)

Browser                # of Connections
Internet Explorer <8   2
Internet Explorer >=8  6
Firefox <3             2
Firefox >=3            6
Safari                 4

References:
http://support.microsoft.com/?scid=kb%3Ben-us%3B282402&x=8&y=8
http://blogs.zdnet.com/Burnette/?p=565
http://www.stevesouders.com/blog/2008/03/20/roundup-on-parallel-connections/

You can change the settings of your browser. For example, when using Firefox, you can use about:config to increase the amount of parallel connections. But since most visitors of your website won’t change this setting and just use a mainstream browser, you can’t rely on this setting. On my current project (online ebanking, so the end user are “normal” people, not whizkids), we have to support browsers back to IE6. This means we have to perform well in browsers that use only 2 parallel connections per web server. Also, statistics indicate that people who use old browsers often are not on high bandwith connections (or are on a corporate network).

So… we had to do some work to enhance user experience.

To wrap up: The issue is simply that too many resources are loaded. It shows up in the chatty waterfall chart.

The solution

With a bit of background knowledge, we can start looking at the solution. We need a way to increase the average download speed/decrease the loading times. As shown in the image, you can see that several small files is not very efficient, so let’s start combining them. This saves some overhead, because we have to download less files. Also, as a nice side effect, compression techniques like GZIP are more efficient on large files.

But are issues, because we developers like to create maintainable code in separate small files. And working with large project teams and version control systems is often a mess when you have large files that everyone is editing concurrently.

Luckily, the solution is easy. Just aggregate all files into one big file. On my project, I’ve used Yahoo’s YUICompressor which not only aggregates, but also minifies the scripts and stylesheets. http://developer.yahoo.com/yui/compressor/

Since we use Apache Maven 2 as our build tool, I’ve integrated compression into our build using a Maven plugin which invokes YUICompressor. I’ve used http://alchim.sourceforge.net/yuicompressor-maven-plugin/

Using the default settings, the plugin only minifies the files. The minified files get the “-min” suffix.

Below is an example of a Maven 2 configuration where the listed files are minified and aggregated into one big “all.js” file. The normal files are still present, but I’ll get into that later.

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>net.sf.alchim</groupId>
        <artifactId>yuicompressor-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compress</goal>
            </goals>
          </execution>
        </executions>        
        <configuration>
          <nosuffix>true</nosuffix>
          <aggregations>
            <aggregation>
              <insertNewLine>true</insertNewLine>
              <output>${project.build.directory}/${project.build.finalName}/js/all.js</output>
              <includes>
                <include>${project.build.directory}/${project.build.finalName}/js/jquery.js</include>
                <include>${project.build.directory}/${project.build.finalName}/js/util.js</include>
                <include>${project.build.directory}/${project.build.finalName}/js/defs.js</include>
                <include>${project.build.directory}/${project.build.finalName}/js/ajax.js</include>
                <include>${project.build.directory}/${project.build.finalName}/js/handlers.js</include>
              </includes>
            </aggregation>
          </aggregations>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

As you can see, I’ve explicitly specified the files to include. You can also use wildcards. I prefer this way because now I’m 100% sure of the ordering in the final aggregation. With wildcards, on the other hand, a simple refactoring (like renaming a file) could silently break the application @runtime. By being explicit, I get an error telling me that a file is missing.

CSS can also be aggregated and minified. And I must say, YUICompressor is really impressive. For example, it doesn’t just remove all comments, but determines per occurrence if it needs to be removed. For example, if you have a Safari Commented Backslash Hack v2 (http://perishablepress.com/press/2006/08/27/css-hack-dumpster/), it will not be removed, since that would break your CSS.

Below is an example where both the JS and CSS files are aggregated into two files: “all.js” and “all.css”.

<plugin>
  <groupId>net.sf.alchim</groupId>
  <artifactId>yuicompressor-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>compress</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <nosuffix>true</nosuffix>
    <aggregations>
      <aggregation>
        <insertNewLine>true</insertNewLine>
        <output>${project.build.directory}/${project.build.finalName}/js/all.js</output>
        <includes>
          <include>${project.build.directory}/${project.build.finalName}/js/jquery.js</include>
          <include>${project.build.directory}/${project.build.finalName}/js/util.js</include>
          <include>${project.build.directory}/${project.build.finalName}/js/defs.js</include>
          <include>${project.build.directory}/${project.build.finalName}/js/ajax.js</include>
          <include>${project.build.directory}/${project.build.finalName}/js/handlers.js</include>
        </includes>
      </aggregation>
      <aggregation>
        <insertNewLine>true</insertNewLine>
        <output>${project.build.directory}/${project.build.finalName}/css/all.css</output>
        <includes>
          <include>${project.build.directory}/${project.build.finalName}/css/global.css</include>
          <include>${project.build.directory}/${project.build.finalName}/css/main.css</include>
          <include>${project.build.directory}/${project.build.finalName}/css/buttons.css</include>
          <include>${project.build.directory}/${project.build.finalName}/css/menu.css</include>
          <include>${project.build.directory}/${project.build.finalName}/css/components.css</include>
        </includes>
      </aggregation>
    </aggregations>
  </configuration>
</plugin>

What about images?

Images can be aggregated too, but this is way more difficult than with scripts. You’ll have to use image sprites, but this first involves creating a sprite (you can do this with online tools if you want), but then the difficulties start, since you need some CSS tricks to “select” the image from the sprite using offsets. This is a real pixel-pain-in-the-ass, but you’ll also enter the realm of cross browser issues here.

This page is a good starting point, but beware, depending on your situation, things may become difficult.
See: http://www.alistapart.com/articles/sprites.

On the other hand, you can of course begin small. For example, creating several sprites for buttons (with hovers and sliding doors you can turn 4 images into 1), boxes and logos. Every improvement is welcome.

Conclusion

Aggregation can greatly improve the performance of your website. On my project, I’ve decreased load times (with an empty cache) from 7 seconds to less than 3 seconds, using only aggregation and script/CSS compression. Using GZIP, caching and some other tweaks, I’m currently even lower. I’m still planning to implement the image sprites.

The moral of this blog is that there is more than the server side and you have to remember, it’s the end user who experiences your application. End users really hate hickups, long load times and web pages that build up in strange ways. They will be annoyed and maybe even lose their trust in the integrity of the application. And when this happens, you’re in deep shit.

Also, remember. Performance tuning differs from other disciplines, like security. With security, there are no (or at least little) compromises. But on the other hand, you can (and should) be happy with every performance improvement as the only valid performance measurement is the happyness of the end user.

Notes

  1. I didn’t remove the original files, since I want to be able to switch @runtime between aggregated files and the originals. This greatly simplifies debugging, especially in production.
  2. Check performance in all browsers. As indicated, browsers have different default values for several settings and different characteristics.
  3. Client side performance is often easy to test. You don’t need heavy load, like you would when doing server side performance testing. You can start with your development machine and a simple testing server is often good enough for a more thorough test.

Laat een reactie achter