Optimizing startup time for GWT hosted mode

by Rob van MarisNovember 30, 2007

When developing GWT (Google Web Toolkit) applications, the GWT hosted mode is an invaluable tool, as it allows for short development cycles. Whenever you change something in the GWT client code and hit the “Refresh” button it will compile the change, reload the application and show the result of my change almost immediately. In theory, that is…
As soon as the GWT client code grows in size, it takes longer and longer to refresh, to the point that it becomes unacceptable.
In this blog I’ll explore this issue, discuss its causes and what you can do about it.

Here’s a true story from a project I’m working on. From the outset we anticipated that next to the IDE the HM is where we were going to spend most of your time while working on GWT client code, since it allows for short development cycles. So we set up our development environments with all server-side code deployed on a Tomcat server, and used the HM to test and debug the GWT client code. Once the hosted mode was started, a refresh would typically take only a few seconds. For a while…

As the application grew, we kept adding new panels, widgets and remote services. We noticed the time it took the app to refresh was going up all the time. When it had grown to almost a minute, waiting for the app to refresh had become almost unbearable. Something needed to be done about it, and we started to investigate why it was so slow.

What’s taking so long: a few surprises

We inserted logging statements in the code to see what was taking so long. The first logging statement was at the very beginning of the onModuleLoad() method in the entry-point class.
I somehow expected that most of the time would be spent loading the application, but instead the first logging statement was reached almost immediately. So all that time it took to refresh the app was actually spent inside the app itself, it was the time it took the app to startup!

Next we inserted more logging statements to see how much time it actually spent on specific parts. The first thing that surprised us was that for each remote service it took 5 seconds to create its client proxy using GWT.create(). Since we had 5 remote services at that time, all initialized during startup, this accounted for 25 seconds of total startup time.

The remaining half was accounted for by the app’s remaining initialization code, mostly creating widgets and wiring them together with event-listeners. We were not surprised it spent half of the startup time here, although we were surprised that this actually took as long as 30+ seconds. Compared to the web mode, were the app starts in a split second, hosted mode seems to be an order of magnitude of 100 times slower. The actual difference may vary depending on the nature of the code, but the consequences remain the same: an app may start up in less than a second in web mode and at the same time take intolerably long to start up in the hosted mode.

Once we understood why a refresh took so long, we took measures to do something about it. We were able to reduce refresh time to less than 15 seconds, and we feel confident it stays like that no matter how big the application may eventually become.

Solutions

Since the application is ultimately deployed in web mode, why bother at all about its hosted mode performance?
Firstly, if its startup-time is allowed to grow continuously, it will eventually get to a point that its not acceptable in web mode either. Optimizing for a fast startup in hosted mode just means dealing with this problem early.
Secondly, the ability that the HM provides to develop GWT client code in short code-refresh-review iterations is one of GWT’s best features. It makes development more productive and more fun. Keeping the HM startup-time at an acceptable level makes the difference between a pleasant experience and a frustrating one for a developer. This should be enough to justify the extra effort!

Here’s what you can do:

#1 Reduce the number of remote services

Since each remote service adds 5 seconds to startup time, it’s an easy optimization, when you have multiple remote services, to replace them all by one. If you want to adhere to the conceptual partitioning in multiple services, you can maintain this partitioning by creating a facade service that just delegates to the original services. Its remote and async interfaces are derived from the original interfaces like this:

// Facade interface for services A and B
public interface UberService extends ServiceA, ServiceB {}
// Facade async interface for services A and B
public interface UberServiceAsync extends ServiceAAsync, ServiceBAsync {}

Once a UberServiceAsync instance is created in the clientcode, it can be used instead of each of the original async interfaces. This way the modification can be applied with minimal impact on the existing code.

#2 Initialize panels lazily

An app typically (but not necessarily) uses something like a DeckPanel or TabPanel to manage a set of panels that you can navigate through. This approach results in all these panels being created and initialized at startup, although initially only one (or none) of them is visible. Initialization of each panel includes all panels they may include (possibly DeckPanels too), event-listeners etcetera, all the way down the panel layout hierarchy. So without additional measures all the widgets in the application will be initialized at startup, regardless of when they are actually displayed.

In order to reduce startup time, lazy initialization of panels must be introduced, i.e. initialization of the panels must be postponed until just before the first time they are displayed. This will spread the overhead of initialization of widgets more evenly over the duration of a user session, instead of having it all at startup, and save you from the initialization overhead for all the panels that are not displayed. Implementing lazy initialization of widgets is straightforward, using an interface like this for a lazy widget:

public interface LazyWidget {
	public Widget createWidget();
}

Next, create a lazy version of DeckPanel (or TabPanel) that holds references to LazyWidget instead of Widget instances, and calls their createWidget() method to initialize the corresponding widget the first time it is displayed. If you take the source of the original DeckPanel as a starting point, this is fairly straighforward, so I won’t go into details here.

Use anonymous inner classes to provide LazyWidget implementations, where the method createWidget() contains the initialization code for the widget. So suppose a snippet of the original code looks like this:

DeckPanel p = new DeckPanel();
Widget w = new SomeWidget();
p.add(w); // Add widget to the panel

After the introduction of lazy initialization it will look like this:

LazyDeckPanel p = new LazyDeckPanel(); // Deckpanel based on lazy widgets.
LazyWidget w = new LazyWidget() {
	public Widget createWidget() {
		return new SomeWidget();
	}
};
p.add(w); // Add lazy widget to the panel

In general there’s no need to make every panel lazily initialized. To get the biggest bang for the buck, consider what the panel hierarchy looks like, and focus your efforts on the panels which are the highest up in the hierarchy or have the most widgets beneath them.

#3 Use hosted mode detection to detect development mode

Hosted mode is development mode, meaning that when the app is in hosted mode, it is safe to assume it is running in a development environment instead of production. A call to GWT.isScript() actually allows you to detect this, it returns false for HM. You can use this feature to switch off functionality that is useful in production, but less so during development.

The code below, for example, navigates to the “Dashboard” only when in web mode, making the “Dashboard” the app’s starting point. In hosted mode this behaviour is disabled, saving us from the overhead of initializing the components used by the dashboard at startup. This is useful, since in development we usually want to go straight to the panel we’re working on instead of the dashboard.

// When in web mode, open dashboard as the first page
if (GWT.isScript()) {
	History.newItem("Dashboard");
}

#4 Use faster hardware

Often, though not always, cutting edge technology requires cutting edge hardware. This certainly applies to GWT development. If you follow my suggestions above, keeping hosted mode refresh-time below 15 seconds is feasable, but only when using state of the art hardware. You need to decide on what to spend money: on new hardware or on waiting.