Performance Tuning On GWT Applications

No comments


In this post we'll show some tips to improve performance of applications created with GWT.

Firstly, we must remember that the
GWT applications are also Web applications. Thus, valid actions to improve the performance of such applications should be observed.


The focus of this post is to talk about performance on the client tier of applications.


First of all, try to use a good profiler tool to help in bottlenecks detection. All major browsers come with a good tool to help developers. An exception is Internet Explorer (that provides a very simple and incomplete tool). However, you can use the excelent Dynatrace to profile your application with IE.

With a good tool, you can detect and measure problems. This article is not intended to replace the use of a tool and the measurement process to detect bottlenecks (sorely needed). Our intention is just to share some knowledge about common practices you should avoid in order to enhance the performance of your GWT applications.

Common Causes of Performance Issues
  • Java Collections API to handle a large amount of data.
The Collections API on GWT is very slow when compared with native javascript arrays and maps. Even when you use java arrays, they are much slower than native javascript ones.

You can read more about this problem here.

To solve it, you can use Crux collection API. It uses the java Collections when runnig on Dev Mode and native arrays and maps for web mode.
  • RPC interfaces that declare very generic parameters
When you are designing interfaces to access the server side of application using GWT RPC, you must care with your parameters and exceptions types.

To implement your method calls, GWT needs to serialize the objects passed as parameters (and also return types, exceptions thrown, etc.). To do this, it creates TypeSerializers for those types.

Behind the scenes, you will have genereted javascript objects to represent your rpc classes. These objects will contain some maps. These maps contains entries pointing to the necessary TypeSerializers.

So if you declare something like:


package com.mycompany.client.remote;

import java.io.Serializable;
import java.util.List;

import com.google.gwt.user.client.rpc.RemoteService;

public interface MyRPCService extends RemoteService 
{
 void myMethod(List< Serializable > parameter) throws Exception;
}

GWT will need to create serializers for all possible exceptions (all ones within your client folder), because your method can throw any exception. It will need to create serializers for all kind of Lists (ArrayList, LinkedList, etc) and for any class that implements Serializable.

It will generate a very big number of TypeSerializers and turn that javascript map of serializers, mentioned earlier, into a very big map, wasting memory and making your application slower.

You should declare your interfaces as much specific as possible, like on the example:


package com.mycompany.client.remote;

import java.util.ArrayList;

import com.google.gwt.user.client.rpc.RemoteService;

public interface MyRPCService extends RemoteService 
{
 void myMethod(ArrayList< String > parameter) throws MyException;
}

Other important point is try to pass only the minimum required as parameters and return types. It will decrease the amount of data serialized and transmited through the network.

  • Excessive number of accesses to the page DOM

Especially in IE, access the DOM is very slow. Hence, we should try to code in order to reduce the number of accesses.

Whenever possible, use variables, as a form of cache to the DOM elements that are frequently accessed, avoiding run many calls to getElementById().

It is essential to monitor the behaviour of your application with a good profiler tool to identify how to reduce these accesses and manipulate the DOM in a proper way. We strongly suggest Dynatrace for it.

Even if you're not manipulating the DOM directly, you must be aware of this problem, since the way your code is written can change how the code generated by GWT accesses the DOM.

As an example, imagine a case where we need to create an HTMLPanel and add several widgets inside it, on specific positions. We can write this code like:
 
public void myMethod()
{
 HTMLPanel htmlPanel = new HTMLPanel(html);
 
 htmlPanel.add(new Button(), "myButton");
 htmlPanel.add(new TextBox(), "myTextBox");
 htmlPanel.add(new ListBox(), "myListBox");
 //other widgets ...
  
 RootPanel.get().add(htmlPanel);
}
And we can also write like:
public void myMethod()
{
 HTMLPanel htmlPanel = new HTMLPanel(html);
 RootPanel.get().add(htmlPanel);
 
 htmlPanel.add(new Button(), "myButton");
 htmlPanel.add(new TextBox(), "myTextBox");
 htmlPanel.add(new ListBox(), "myListBox");
 //other widgets ...
}


The two versions are very similar, but the performance is very different. The first version is much slower.

The reason for this is how the widget HTMLPanel works. The method add(Widget, String) adds the widget within the element with id entered as the second parameter of the method.

To find this element, the HTMLPanel runs the getElementById command. The point is that the HTMLPanel is not attached to the DOM yet, what would cause the method getElementById returns nothing. Thus, when the add method is called before the panel be attached to the DOM, it attaches itself to a temporary element in DOM, runs the getElementById method and then removes itself from the temporary element.

Thus, the first code shown generates much more changes in the DOM than the second (becoming slower), although the end result is the same.

You must also be careful when choosing the widgets to your page. To present a large amount of data, use the GWT new Cell widgets (in the place of the old Grids and Tables) that are much faster, because they build the widget HTML as a string before attach it, making much less accesses to DOM.

  • Build all widgets eagerly
A very common problem on applications is to build all widgets at the same time, when the page loads. It is a very bad practice that makes your application slow.

You must always render strictly what is necessary on each moment. Try to create your widgets lazily, when they become necessary.

Using GWT's LazyPanel you can make your application faster, by rendering some widgets only when they are needed. Widgets whose contents are conditionally visible (like DeckPanels and DisclosurePanels) can make an excelent usage of the feature provided by the lazy mechanism.




Our final warning to you: Measure your application!



Using Dynatrace you can detect easily where your application has a problem. With this information, you can try to make improvements. Don't start the optimization process without measure first. You can have a big effort to improve a piece of code that will not bring any real gain for the application. You must know the problem to define where you will act.

Lets see an example. We have some components that need to make a lot of string comparations. These comparations are made inside a very big loop.

When GWT compiles the code to javascript, it will produce the following code for the String.equals() method:

function java_lang_String_$equals__Ljava_lang_String_2Ljava_lang_Object_2Z(this$static,other)
{
 if(!(other != null && 
            com_google_gwt_lang_Cast_canCast__IIZ(other.java_lang_Object_typeId$, 1)))
 {
  return false;
 }
 return  String(this$static) == other;
}

Any javascript programmer would see this code and think: "To compare two strings I could just write one line of code and use the === operator!!!". Yes, it is wright, but GWT implements the method in order to make it compatible with the java API. The method equals() receives an Object as parameter, and it needs to handle any type differences. It checks if the parameter can be cast to String (if it is another String or a subclass of it.)

However, if we detected that the loop with comparations is creating a performance issue, we could use a trick. We could create a static method to do this comparation faster. See the following example:

public class StringUtils
{
  public static boolean unsafeEquals(String a, String b)
  {
   if (GWT.isScript())
   {
    return a==b;
   }
   else
   {
    return a.equals(b);
   }
  }
}

Then, we just change the loop to something like:


for (int i=0; i < veryBigIndex; i++)

 if (StringUtils.unsafeEquals(a, b))
 {
  // ...
 }
}

The result is that the equals method will be used on DevMode and the final javascript (for web mode) will be generated as:


for (var i=0; i < veryBigIndex; i++)
{
 if (a == b)
 {
  // ...
 }
}


We don´t need to care with type parameter differences because the unsafeEquals method signature declares its parameters as Strings, so the compiler will ensure the types compatibility, once String is a final class and can not be overridden.

Note that unsafeEquals is not checking for null parameters (that's why it is called "unsafe").

That example was extracted from a real case, where we obtain a considerable gain.

However, it is important to emphasize that we could not use this kind of trick indiscriminately. It would turn our code more complex to read and mantain. It must be used when we are faced with a performance issue, detected previously with a profiler tool.

Besides all the problems mentioned above, you need to remember that we are talking about web applications. The discussion on this post is focused on problems strictly related with GWT, but we also need to think on all aspects of the web apllication.
We need try to reduce the number of requests, use cache and expires headers properly, and follow all good practices related with this kind of development.

A very good guide is available here. We strongly suggest a reading on it.



      No comments :

      Post a Comment