Monday, December 13, 2010

High-performance Web development with Google Web Toolkit and Eclipse Galileo

Michael Galpin, Software architect, eBay
Summary:  By now, you have probably heard of Google Web Toolkit (GWT). You know
that it lets you write your Web applications in the Java™ programming language that is compiled into
JavaScript to run in Web browsers. This lets you be more productive by taking
advantage of Java's static typing and great tools like Eclipse. You have may
seen some of the useful and stylish widgets built on top of GWT. What
you may not know is that GWT lets you create high-performance Web
applications. In this article, we look at how you can use the Google
Plug-in with Eclipse Galileo to tap into the performance features of GWT, such
as compiler optimizations, deferred binding, and Ajax optimizations. Developer
performance is still an important part of GWT, so along the way, we will also
show you how tweak the Google Plug-in for Eclipse to increase your
productivity.

Prerequisites

In this article, we will take a look at several features of GWT and examine
how they enable you to build high-performance Web applications. This
article is not an introduction to GWT, so prior experience with GWT
is assumed. It is also assumed that
you know Java technology and that you are familiar with JavaScript, CSS, and HTML.
It is recommended that you use the latest version of GWT: V1.7 was
used for this article. This article also uses the Google Plug-in for
Eclipse. V1.1 of the Google Plug-in was used with Eclipse V3.5
(Galileo) for this article. We will also use the Firebug
plug-in for Mozilla Firefox. V1.4.2 of Firebug was used, along with
Firefox V3.5.2 (see Resources).


GWT is well known for its ability to compile Java into JavaScript,
enabling Java developers to develop dynamic Web applications. All the code
is compiled into JavaScript, and many of GWT's performance features were
designed to make JavaScript run faster in the browser. There are several
such features in GWT, including browser-specific optimizations and
streamlined Ajax, but most of the features start with GWT's Java-to-JavaScript compiler. So that is where we will begin our exploration of
GWT's performance-related features.

Compiler optimizations

The GWT compiler is what translates your Java code into JavaScript
that runs in the browser. However, it does much more than simply translate
Java technology into JavaScript. The compiler does numerous optimizations to make
your code run faster. However, it can be difficult to understand exactly
what is going on with these optimizations, as the JavaScript emitted by
GWT is obfuscated and very hard to read. First, we want to instruct the
GWT compiler to produce some human-readable JavaScript, so we can get a
better idea of exactly what optimizations the compiler is making.

The GWT compiler has three modes of operation. The default mode is
obfuscated, in that the GWT compiler will emit obfuscated JavaScript. This
JavaScript is not just difficult to read but it is compressed. This makes it
smaller, so it loads faster over the Internet. The smaller size also helps
the browser to parse the JavaScript quicker.

Some of you might be thinking
that sending compressed JavaScript over the wire makes little difference
since most Web servers send JavaScript using gzip compression, as gzip
compression is supported by any modern Web browser. However, not only does
the GWT compiler compress the JavaScript but it does so in a way that is
engineered with gzip compression in mind. In other words,
GWT-obfuscated JavaScript is highly gzip-compressible, despite being already
compressed. Thus, if your Web server is not using gzip, you will
obviously get a big speed boost by using GWT obfuscation. However, even if
your Web server is using gzip, you will still get a significant boost from
GWT obfuscation.

So for production code, we clearly want the GWT compiler set to emit
obfuscated JavaScript. However, this clearly makes it nearly impossible to
read the emitted JavaScript. To see this, take a look at some GWT-obfuscated JavaScript in Listing 1.


Listing 1. Obfuscated JavaScript
function qH(){return np}
function mH(){}
_=mH.prototype=new mu;_.gC=qH;_.tI=0;function
uH(){uH=ZH;sH={};tH=[];sH[LM]=[Is,Hs,Js];sH[JM]=[rt,qt,st];Xv(tH,yn,LM);Xv(tH,To,JM)}
var sH,tH;function AH(a){a.b=oH(new mH);return a}
function BH(a){var b,c,d,e,f,g,h,i,j,k;g=ox(new cx,MM);f=OA(new FA);j=XH(new
 VH,NM,OM);KA(f,j.b+sJ+j.c);pw(g.B,PM,true);Zw(gA(QM),f);Zw(gA(RM),g);f.B.focus()
;k=Jg(f.B,NJ).length;k>0&&JA(f,0,k);c=py(new my);Lf((tf(),c.b.B),SM);c.o=true;
b=ox(new cx,TM);b.B.id=UM;i=Py(new Ny);h=Ty(new My);d=UA(new RA);pw(d.B,VM,true);
VA(d,Uy(new My,WM));VA(d,i);VA(d,Uy(new My,XM));VA(d,h);d.b=(kz(),jz);VA(d,b);
Ax(c.j,d);Mx(c);vw(b,FH(new DH,c,g),(sh(),rh));e=KH(new IH,a,g,f,i,h,c,b);
vw(g,e,rh);vw(f,e,(hi(),gi))}
function CH(){return rp}
function xH(){}



Luckily, it is easy to coax the GWT compiler into creating some much more
human-eadable JavaScript. You can simply pass the compiler a
-style=PRETTY argument. This is made even
easier using the Google Plug-in for Eclipse. When you trigger a GWT
compile, it will bring up the dialog shown in Figure 1.



Figure 1. GWT compiler options

Screen shot showing GWT compiler options





To see the JavaScript that the compiler is emitting,
just choose the Pretty setting shown in Figure 1. Now the code will look
more like what is shown in Listing 2.


Listing 2. Pretty JavaScript
var $wnd = parent;
var $doc = $wnd.document;
var $moduleName, $moduleBase;
var $strongName = '21B409FCD39529C5A9DB925F7D8D9A95';
var $stats = $wnd.__gwtStatsEvent ? function(a) {return 
   $wnd.__gwtStatsEvent(a);} : null;
$stats && $stats({moduleName:'gwtperf',subSystem:'startup',evtGroup:
'moduleStartup',millis:(new Date()).getTime(),type:'moduleEvalStart'});
var _;
function nullMethod(){
}

function equals(other){
  return this === (other == null?null:other);
}

function getClass_0(){
  return Ljava_lang_Object_2_classLit;
}

function hashCode_0(){
  return this.$H || (this.$H = ++sNextHashId);
}

function toString_0(){
  return (this.typeMarker$ == nullMethod || this.typeId$ ==
2?this.getClass$():Lcom_google_gwt_core_client_JavaScriptObject_2_classLit)
.typeName + '@' + toPowerOfTwoString(this.typeMarker$ == nullMethod || this.typeId$
== 2?this.hashCode$():this.$H || (this.$H = ++sNextHashId), 4);
}

function Object_0(){
}

_ = Object_0.prototype = {};
_.equals$ = equals;
_.getClass$ = getClass_0;
_.hashCode$ = hashCode_0;
_.toString$ = toString_0;
_.toString = function(){
  return this.toString$();
}
;



This is still highly optimized JavaScript, but is much easier to
understand. Of course, this makes a big difference in the size of the
JavaScript being created. We can examine this by using the Firebug plug-in
for Firefox, as shown in Figure 2.



Figure 2. Comparing JavaScript file
size: Obfuscation vs. Pretty


Screen shot comparing JavaScript file size: Obfuscation vs. Pretty





Figure 2 shows the same GWT application (the starter project created by the
Google Plug-in) compiled with obfuscated JavaScript (at the top) and
compiled with pretty JavaScript (bottom.) As you can see from the figure,
the JavaScript size goes from 58 KB to 146 KB when going from obfuscated
to pretty.

Now we can take a look at some code to see how the GWT compiler optimizes
it. One of the ideas behind GWT is that you can write your code using
software engineering best practices. You can introduce the right kind of
abstractions that will make your code robust and maintainable. Meanwhile,
GWT will compile into very fast code. Let's take a sample trivial
class for modeling users, as shown in Listing 3.


Listing 3. A Person class
public class Person {
    final String firstName;
    final String lastName;
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    public String getName(){
        return firstName + " " + lastName;
    }
}



Now, we will alter the following code from the GWT start project, shown in
Listing 4.


Listing 4. Original GWT start project code
final TextBox nameField = new TextBox();
nameField.setText("GWT User");



Now we will simply use our Person object in
place of the hard-coded string shown above. This change is shown in
Listing 5.


Listing 5. Modified to use the Person
final TextBox nameField = new TextBox();
final Person user = new Person("GWT", "User");
nameField.setText(user.getName());



This is a simple change, but it is easy to imagine this being a useful
abstraction for an application. However, there could be a price to pay in
terms of performance. There is (potential) overhead in object creation and
in method dispatch. Let's take a look at how the GWT compiler can help
out. Listing 6 shows the compiled version of the original code.


Listing 6. Original code, compiled to JavaScript
nameField = $TextBox(new TextBox());
nameField.element['value'] = 'GWT User' != null?'GWT User':'';



Now let's take a look at what Listing 5 looks like when compiled to
JavaScript. This is shown in Listing 7.


Listing 7. Modified code, compiled to JavaScript
user = $Person(new Person(), 'GWT', 'User');
  $setText_1(nameField, user.firstName + ' ' + user.lastName);



The first line of code is calling the JavaScript constructor for the
Person class. This is a straight translation of
the Java code. The second line of code has been changed a bit. Instead of
calling the getName() method on the
Person instance, the method that has been inlined.
That is the call to the method has been replaced by the body of the
method. This is a common optimization done by compilers —
historically,
C/C++ and Java compilers — but is a trick that the GWT compiler takes
full advantage of as well to produce fast JavaScript.

It is common in object-oriented development to abstract out a common
interface that may have multiple implementations and write code that
works with the interface, making it more reusable. This is another
useful engineering abstraction that can impose extra lookups and method
dispatch, hurting performance. Let's take a look at how the GWT
compiler can help with that. You can use Eclipse's refactoring tools to
easily extract an interface from the Person
class in Listing 3. Let's call that a Thing. It is shown in Listing 8.


Listing 8. Refactored with Thing interface
public interface Thing {
    String getName();
}
public class Person implements Thing {
    final String firstName;
    final String lastName;
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    public String getName(){
        return firstName + " " + lastName;
    }
}



Now the client code that uses the Person should
be changed to use the Thing interface. This is
shown in Listing 9.


Listing 9. Refactored client code
Thing user = new Person("GWT", "User");
nameField.setText(user.getName());



What happens to the compiled code? Do we pay a performance price? Take
a look for yourself in Listing 10.


Listing 10. Refactored code, recompiled
user = $Person(new Person(), 'GWT', 'User');
$setText_1(nameField, user.firstName + ' ' + user.lastName);



As you can see, there are no changes at all. What if we have two
implementations of the interface that are both used? The code for this is
shown in Listing 11.


Listing 11. Multiple implementations of Thing
public class Company implements Thing {
    private final String name;
    public Company(String name){
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}
// client code
final TextBox nameField = new TextBox();
Thing user = new Person("GWT", "User");
Thing userCompany = new Company("ACME");
nameField.setText(userCompany.getName() + " " + user.getName());



Listing 12 shows the JavaScript emitted by the GWT compiler.


Listing 12. Multiple implementations compiled away
user = $Person(new Person(), 'GWT', 'User');
userCompany = $Company(new Company(), 'ACME');
$setText_2(nameField, userCompany.name_0 + ' ' + (user.firstName + ' ' + user.lastName));
            



As you can see, the compiler still removes the interface and still inlines
each implementation of the getName() method.
This is still quite optimal code. However, there are things you can
do that can prevent optimizations, as shown in Listing 13.


Listing 13. Defeating the compiler
private String mkString(Collection<Thing> things, char separator){
    StringBuilder sb = new StringBuilder();
    for (Thing thing : things){
        sb.append(thing.getName());
        sb.append(separator);
    }
    return sb.deleteCharAt(sb.length()).toString();
}
// client code
final TextBox nameField = new TextBox();
Thing user = new Person("GWT", "User");
Thing userCompany = new Company("ACME");
nameField.setText(mkString(Arrays.asList(user, userCompany), ' '));



In Listing 13, we have introduced a new abstraction: a helper function for
taking a collection of Thing objects and
concatenating the result of calling getName()
on each Thing. The separator is also abstracted
out as a parameter to this function. Now let's look at the compiled
JavaScript in Listing 14.


Listing 14. Compiled mkString code
function $mkString(things, separator){
  var sb, thing, thing$iterator;
  sb = $StringBuilder(new StringBuilder());
  for (thing$iterator = $AbstractList$IteratorImpl(new AbstractList$IteratorImpl(), 
things); thing$iterator.i < thing$iterator.this$0.size_0();) {
    thing = dynamicCast($next_1(thing$iterator), 16);
    $append_2(sb, thing.getName());
    sb.impl.string += String.fromCharCode(separator);
  }
  return $deleteCharAt(sb, sb.impl.string.length).impl.string;
}
// client code
user = $Person(new Person(), 'GWT', 'User');
userCompany = $Company(new Company(), 'ACME');
$setText_1(nameField, $mkString($Arrays$ArrayList(new Arrays$ArrayList(), 
initValues(_3Lorg_developerworks_gwt_client_Thing_2_classLit, 0, 16, 
  [user, userCompany])), 32));



The code in Listing 14 is of similar complexity as the Java source code.
Notice that inside the loop, a function called
dynamicCast is called. This is JavaScript used
to check if the object passed to it can be cast to an object of a given
type. In this case, it will check if the object is a
Person or a Company,
as these are the only objects implementing
Thing. By introducing code written
against the interface and having multiple implementations of the
interface, there are fewer optimizations that can be done by the GWT
compiler.

So far, all of the optimizations that we have looked at were language-level
optimizations made by the GWT compiler. There are other more
browser-specific optimizations that can be done by GWT. These types of
optimizations are generally lumped under the umbrella concept known as
deferred binding.

Deferred binding

Web developers have been suffering from the variations across Web browsers
ever since Mosaic was no longer the only browser around. One of the great
appeals of the many JavaScript frameworks out there is to smooth over the
differences between browsers to make your life a little more
sane. There are two common approaches to this. First, you can write code
that is portable across browsers. This is a least-common denominator
approach, which is at best slightly less than optimal, but more often, it is
far from optimal. The other approach is to sniff the browser and use the
optimal code for each browser. This causes a lot of spaghetti code and
implies that there will be a lot of code shipped to the browser that is
never executed on the browser.

GWT's deferred binding architecture allows it to compile multiple versions
of JavaScript for the various browsers. A small piece of JavaScript is
downloaded to the browser to sniff the browser, then the optimized
JavaScript is downloaded. If you look back at Figure 2, you will notice a
.nocache.js file that was downloaded. This is the browser-sniffing code,
and in this case, was 4 KB. By default, four permutations are
checked: Opera, Safari, Gecko (Firefox V2 or less), Gecko V1.8 (Firefox
V3+), Internet Explorer V6, and Internet Explorer V8.

The classic example of an API that differs greatly across browsers is
setting the innerText property of an element.
This is done in the starter project in the callback handler to the remote
procedure call to the server. The Java code is quite simple and is shown
in Listing 15.


Listing 15. Setting text in GWT
public void onSuccess(String result) {
    dialogBox.setText("Remote Procedure Call");
    serverResponseLabel.removeStyleName("serverResponseLabelError");
    serverResponseLabel.setHTML(result);
    dialogBox.center();
    closeButton.setFocus(true);
}



Now let's take a look at what the GWT compiler will emit for various
browsers. To figure out what file is being compiled for each browser
permutation, take a peek into the .nocache.js file and look for a section
of code that looks similar to the code shown in Listing 16.


Listing 16. GWT's browser-sniffing code
if (!strongName) {
  try {
    unflattenKeylistIntoAnswers(['opera'], 
'D1B884746B9C511E12378A55F9FD97E2.cache.html');
    unflattenKeylistIntoAnswers(['safari'], 
'12DC532DA52018F17FA7F84F7137102A.cache.html');
    unflattenKeylistIntoAnswers(['gecko1_8'], 
'0986E60F243CC620FA7138AB06F221EB.cache.html');
    unflattenKeylistIntoAnswers(['gecko'], 
'CF1F7CBAF43D18B03F82260D99CB1803.cache.html');
    unflattenKeylistIntoAnswers(['ie8'], 
'1EE88964C0A866A7F2887C02F69F64D3.cache.html');
    unflattenKeylistIntoAnswers(['ie6'], 
'5395DF4A8135D37430AAE1347158CE76.cache.html');
    strongName = answers[computePropValue('user.agent')];
  }
  catch (e) {
    return;
  }
}



Now you can match up each of the keys ('opera', 'safari', etc.) to the
generated files. Using this, now we can find the version of the
onSuccess method from Listing 15 that was
compiled for Internet Explorer V6, shown in Listing 17.


Listing 17. Compiled for Internet Explorer V6
function $onSuccess(this$static, result){
  ($clinit_11() , this$static.val$dialogBox.caption.element)
.innerText = 'Remote Procedure Call';
  setStyleName(this$static.val$serverResponseLabel.element, 
'serverResponseLabelError', false);
  this$static.val$serverResponseLabel.element.innerHTML = result || '';
  $center(this$static.val$dialogBox);
  $setFocus(this$static.val$closeButton, true);
}



For Internet Explorer V6, GWT uses the optimal API for Internet Explorer
V6: using the innerText property
of the element. Now let's compare this to the Gecko V1.8+ permutation shown
in Listing 18.


Listing 18. Compiled for Firefox V3+
function $onSuccess(this$static, result){
  ($clinit_11() , this$static.val$dialogBox.caption.element)
.textContent = 'Remote Procedure Call';
  setStyleName(this$static.val$serverResponseLabel.element, 
'serverResponseLabelError', false);
  this$static.val$serverResponseLabel.element.innerHTML = result || '';
  $center(this$static.val$dialogBox);
  $setFocus(this$static.val$closeButton, true);
}



For newer versions of Firefox, GWT compiles the Java code into JavaScript
that uses the textContent property of the
element. This is a simple example, but you could easily imagine using
something like innerText multiple times in your
application code.

If you develop much with GWT, you quickly become aware of deferred binding
— but not always for the best reason. As we have seen, GWT compiles a
different version of JavaScript for each permutation of your application.
These permutations are also how GWT localizes your code. By default, there
are six browser variations and one language. If you needed to support
three languages instead of one, there would be 18 permutations and
18 versions of the JavaScript. This can often lead to long compiles
that can really cut into your productivity. Of course, one
way to overcome this is to do most of your work in Hosted Mode, where you
do not compile to JavaScript at all. This is by far the easiest way to
debug your code. However, if you need to compile, you can greatly
reduce the compile time by telling GWT to only compile for one browser
(whatever browser you like to use for testing.) All you have to do is
modify the module XML configuration file for your application, as shown in
Listing 19.


Listing 19. Configure GWT for one browser
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" 
"http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core
/src/gwt-module.dtd">
<module rename-to='gwtperf'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.standard.Standard'/>
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

  <!-- Other module inherits                                      -->

  <!-- Specify the app entry point class.                         -->
  <entry-point class='org.developerworks.gwt.client.GwtPerf'/>
  <set-property name="user.agent" value="gecko1_8"/>
</module>



The key here is the next-to-last line: the
set-property tag. We simply set the
user.agent property to
gecko1_8 and now GWT will only do one compile
that produces JavaScript targeted for Firefox V3+. We have now seen some of
the many ways that GWT produces just the right JavaScript that will run
the fastest in the user's browser.

High-speed Ajax

Ajax has become ubiquitous on the Web, and it is an essential part of any
Web application. When the Ajax term was coined, the X in Ajax stood for
XML. This was assumed to be the format for data being sent between
browsers and servers. However, many Ajax applications actually receive
HTML from the servers that they communicate with. This is usually done
because it is the easiest thing to implement. It is far from optimal,
though.

Some Ajax application servers send back data in the form of XML. This is a
huge improvement over HTML, but it is also suboptimal. XML is a bulky
format and can be awkward to parse using JavaScript. This leads to too
many bytes being sent over the wire, and too much JavaScript has to be
executed that the user has to wait for. Many Web applications have
switched to using JSON. This is an improvement.

GWT includes both client-side and server-side components for Ajax, so
you might wonder what data format GWT uses for Ajax. It probably comes as
no surprise that GWT uses a unique format that is highly optimized. To
take a look at it, Firebug once again comes in handy, as shown in Figure
3.



Figure 3. Monitor Ajax traffic with
Firebug


Screen shot depicting monitoring Ajax traffic with Firebug





Firebug shows data sent to the server and the data returned by
the server. Take a more detailed look in Listing 20.


Listing 20. GWT request and response data
Request: 
5|0|6|http://localhost:8080/gwtperf/|29F4EA1240F157649C12466F01F46F60|
org.developerworks.gwt.client.GreetingService|greetServer|java.lang.String|
IBM developerWorks|1|2|3|4|1|5|6|
Response:
//OK[1,["Hello, IBM developerWorks!<br><br>I am running Google App Engine
Development/1.2.2.<br><br>It looks like you are using:<br>Mozilla/
5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/
3.5.2"],0,5]



Of course, the Java code you would write is quite simple. GWT provides a simple programming model at design time, but a highly
optimized runtime implementation.

Summary

This article has shown many of the performance optimizations GWT
provides for developers and how to take advantage of the Google Plug-in
for Eclipse Galileo. GWT makes it easy to write dynamic Web applications
that are also high-performance Web applications. This will continue to get
even better. The upcoming GWT V2.0 release includes several
new features, such as code-splitting and resource bundles, that go even
further toward improving the performance of Web applications created with
GWT. You can get early access to these features by building the GWT trunk
source code.

Download
DescriptionNameSizeDownload method
Source codeos-eclipse-googlegalileo-source.zip36KBHTTP
Information about download methods


Resources
Learn
Get products and technologies

No comments:

Post a Comment